双击 Angular 指令
Double click Angular directive
我在 Angular 7 中有以下模板:
<ul class="posts">
<li *ngFor="let post of posts">
<h2>{{post.title}}</h2>
<a (click)="delete(post)">Delete Post</a>
</li>
</ul>
我想创建一个确认指令用作:
<a (click)="delete(post)" confirm="Confirm delete" class="delete">Delete Post</a>
点击瞬间(一个就够)变为:
<a (click)="delete(post)" confirm="Confirm delete" class="delete confirm">Confirm delete</a>
那么会发生什么:
- 锚的文本从 "Delete Post" 更改为内部确认,例如"Confirm Delete";
- Class "confirm" 添加到锚 CSS 类;
- Delete(post) 方法仅在点击 "confirm mode";
后调用
- 点击 "confirm mode" 或 5 秒后没有被点击,它会回到原来的状态:
<a (click)="delete(post)" confirm="Confirm delete" class="delete">Delete Post</a>
这可以用指令完成吗?
import { Directive } from '@angular/core';
@Directive({
selector: '[confirm]'
})
export class ConfirmDirective {
constructor(el: ElementRef) {
el.nativeElement ...
}
}
我开始创建指令,但我真的不知道该怎么做。
您可以通过带有 @HostBinding
和 @HostListener
的指令添加宿主元素行为绑定和侦听器,以及绑定到指令本身的任意 @Input
:
@Directive({
selector: '[confirm]'
})
export class ConfirmDirective {
@HostListener('dblclick') onDoubleClick(event) {
// .. do double click logic, just like binding (dbclick) to an element
}
@HostBinding('class.confirm') confirmStyle: boolean; // toggles class on host, just like with template binding
@Input('confirm') confirm: boolean; // State from outside the directive that can be bound to the directive attribute directly, i.e. 'click to confirm' box
constructor(el: ElementRef) {
el.nativeElement ...
}
}
如果您希望直接添加 HTML / 带有确认按钮指令的标记,这种行为最好用组件包装。组件用于视图;指令是针对行为的。一个想法是将确认对话框(?)包装到指令可以调用的服务中。
你可以使用 rxjs takeUntil,
posts: any[] = [
{ id: 1, title: 'post 1', deleteText: 'Delete Post' },
{ id: 2, title: 'post 2', deleteText: 'Delete Post' }
];
delete(post) {
post.deleteText = 'Click to Confirm';
post.css = 'custom';
let confirm$ = fromEvent(document.getElementById('post-' + post.id), 'click');
let timer$ = timer(5000)
confirm$
.pipe(
takeUntil(timer$)
)
.subscribe(() => {
console.log('ready to delete');
this.posts = this.posts.filter(p => p.id !== post.id);
});
timer$
.subscribe(() => {
if (this.posts.find(p => p.id === post.id)) {
console.log('timer is up, abort delete');
post.deleteText = 'Delete Post';
post.css = '';
}
});
}
HtML:
<ul class="posts">
<li *ngFor="let post of posts">
<h2>{{post.title}}</h2>
<a (click)="delete(post)" [ngClass]="post.css" [id]="'post-'+post.id"> {{post.deleteText}}</a>
</li>
</ul>
演示:https://stackblitz.com/edit/angular-7-master-xg8phb
(还需要管理订阅)
更新:
如果你真的想,你实际上可以用一个指令来做。试试这个:
import { Directive, ElementRef, Input, Renderer2, OnInit } from '@angular/core';
import { Observable, Subject, BehaviorSubject, timer, fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Directive({
selector: '[confirm]'
})
export class ConfirmDirective implements OnInit {
@Input('confirm') delete: Function;
private confirm$ = fromEvent(this.el.nativeElement, 'click');
private confirmTimeout: number = 5000;
private timer$: Observable<number>;
private isConfirming = new BehaviorSubject<boolean>(false);
private isConfirming$ = this.isConfirming.asObservable();
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
this.isConfirming$.subscribe((isConfirming) => this.setLabel(isConfirming));
this.confirm$.subscribe((event: any) => this.doConfirm());
}
setLabel(isConfirming: boolean): void {
// set the correct element text and styles
let text: any;
let textEl = this.renderer.createElement('span');
if (this.el.nativeElement.firstChild) {
this.renderer.removeChild(this.el.nativeElement, this.el.nativeElement.firstChild);
}
if (this.isConfirming.value) { // we are confirming right now
text = this.renderer.createText('Please confirm delete');
this.renderer.addClass(this.el.nativeElement, 'delete');
} else {
text = this.renderer.createText('Delete');
this.renderer.removeClass(this.el.nativeElement, 'delete');
}
this.renderer.appendChild(this.el.nativeElement, text);
}
doConfirm(): void {
if (this.isConfirming.value === false) { // start confirming
this.timer$ = timer(this.confirmTimeout);
this.isConfirming.next(true);
// start the timer
this.timer$
.pipe(
takeUntil(this.confirm$) // stop timer when confirm$ emits (this happens when the button is clicked again)
)
.subscribe(() => {
this.isConfirming.next(false); // timeout done - confirm cancelled
});
} else { // delete confirmation
this.isConfirming.next(false);
this.delete(); // this is the delete action that was passed to the directive
}
}
}
您可以将它应用于这样的元素,将实际的 delete
方法作为参数传递。
<button type="button" [confirm]="delete"></button>
工作示例:https://stackblitz.com/edit/angular-wdfcux
旧答案:
不确定指令是否是最好的方法。它可能会完成,但你必须以某种方式拦截点击处理程序 and/or 将删除方法传递给它。估计会很乱。
我可能会为删除按钮创建一个组件并在那里处理它(好吧,实际上这是一个谎言,如果这是我,我会使用本机 confirm
对话框并完成它,但是你不想)。
像这样:
import { Component } from '@angular/core';
import { Observable, Subject, timer } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'delete-button',
template: `<button type="button" (click)="delete()" [ngClass]="{ delete: isConfirming }">{{ label }}</button>`,
styles: ['.delete { background-color: teal; color: white; } ']
})
export class DeleteButtonComponent {
private confirmTimeout: number = 5000;
private timer$: Observable<number>;
private cancelTimer = new Subject();
public isConfirming: boolean = false;
constructor() {}
get label(): string {
return this.isConfirming
? 'Please confirm delete'
: 'Delete'
}
delete() {
if (!this.isConfirming) {
this.timer$ = timer(this.confirmTimeout);
this.isConfirming = true;
this.timer$
.pipe(
takeUntil(this.cancelTimer)
).subscribe(() => {
this.isConfirming = false;
}, null, () => this.isConfirming = false);
} else {
this.cancelTimer.next();
// really delete
}
}
}
我在 Angular 7 中有以下模板:
<ul class="posts">
<li *ngFor="let post of posts">
<h2>{{post.title}}</h2>
<a (click)="delete(post)">Delete Post</a>
</li>
</ul>
我想创建一个确认指令用作:
<a (click)="delete(post)" confirm="Confirm delete" class="delete">Delete Post</a>
点击瞬间(一个就够)变为:
<a (click)="delete(post)" confirm="Confirm delete" class="delete confirm">Confirm delete</a>
那么会发生什么:
- 锚的文本从 "Delete Post" 更改为内部确认,例如"Confirm Delete";
- Class "confirm" 添加到锚 CSS 类;
- Delete(post) 方法仅在点击 "confirm mode";
后调用
- 点击 "confirm mode" 或 5 秒后没有被点击,它会回到原来的状态:
<a (click)="delete(post)" confirm="Confirm delete" class="delete">Delete Post</a>
这可以用指令完成吗?
import { Directive } from '@angular/core';
@Directive({
selector: '[confirm]'
})
export class ConfirmDirective {
constructor(el: ElementRef) {
el.nativeElement ...
}
}
我开始创建指令,但我真的不知道该怎么做。
您可以通过带有 @HostBinding
和 @HostListener
的指令添加宿主元素行为绑定和侦听器,以及绑定到指令本身的任意 @Input
:
@Directive({
selector: '[confirm]'
})
export class ConfirmDirective {
@HostListener('dblclick') onDoubleClick(event) {
// .. do double click logic, just like binding (dbclick) to an element
}
@HostBinding('class.confirm') confirmStyle: boolean; // toggles class on host, just like with template binding
@Input('confirm') confirm: boolean; // State from outside the directive that can be bound to the directive attribute directly, i.e. 'click to confirm' box
constructor(el: ElementRef) {
el.nativeElement ...
}
}
如果您希望直接添加 HTML / 带有确认按钮指令的标记,这种行为最好用组件包装。组件用于视图;指令是针对行为的。一个想法是将确认对话框(?)包装到指令可以调用的服务中。
你可以使用 rxjs takeUntil,
posts: any[] = [
{ id: 1, title: 'post 1', deleteText: 'Delete Post' },
{ id: 2, title: 'post 2', deleteText: 'Delete Post' }
];
delete(post) {
post.deleteText = 'Click to Confirm';
post.css = 'custom';
let confirm$ = fromEvent(document.getElementById('post-' + post.id), 'click');
let timer$ = timer(5000)
confirm$
.pipe(
takeUntil(timer$)
)
.subscribe(() => {
console.log('ready to delete');
this.posts = this.posts.filter(p => p.id !== post.id);
});
timer$
.subscribe(() => {
if (this.posts.find(p => p.id === post.id)) {
console.log('timer is up, abort delete');
post.deleteText = 'Delete Post';
post.css = '';
}
});
}
HtML:
<ul class="posts">
<li *ngFor="let post of posts">
<h2>{{post.title}}</h2>
<a (click)="delete(post)" [ngClass]="post.css" [id]="'post-'+post.id"> {{post.deleteText}}</a>
</li>
</ul>
演示:https://stackblitz.com/edit/angular-7-master-xg8phb
(还需要管理订阅)
更新:
如果你真的想,你实际上可以用一个指令来做。试试这个:
import { Directive, ElementRef, Input, Renderer2, OnInit } from '@angular/core';
import { Observable, Subject, BehaviorSubject, timer, fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Directive({
selector: '[confirm]'
})
export class ConfirmDirective implements OnInit {
@Input('confirm') delete: Function;
private confirm$ = fromEvent(this.el.nativeElement, 'click');
private confirmTimeout: number = 5000;
private timer$: Observable<number>;
private isConfirming = new BehaviorSubject<boolean>(false);
private isConfirming$ = this.isConfirming.asObservable();
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
this.isConfirming$.subscribe((isConfirming) => this.setLabel(isConfirming));
this.confirm$.subscribe((event: any) => this.doConfirm());
}
setLabel(isConfirming: boolean): void {
// set the correct element text and styles
let text: any;
let textEl = this.renderer.createElement('span');
if (this.el.nativeElement.firstChild) {
this.renderer.removeChild(this.el.nativeElement, this.el.nativeElement.firstChild);
}
if (this.isConfirming.value) { // we are confirming right now
text = this.renderer.createText('Please confirm delete');
this.renderer.addClass(this.el.nativeElement, 'delete');
} else {
text = this.renderer.createText('Delete');
this.renderer.removeClass(this.el.nativeElement, 'delete');
}
this.renderer.appendChild(this.el.nativeElement, text);
}
doConfirm(): void {
if (this.isConfirming.value === false) { // start confirming
this.timer$ = timer(this.confirmTimeout);
this.isConfirming.next(true);
// start the timer
this.timer$
.pipe(
takeUntil(this.confirm$) // stop timer when confirm$ emits (this happens when the button is clicked again)
)
.subscribe(() => {
this.isConfirming.next(false); // timeout done - confirm cancelled
});
} else { // delete confirmation
this.isConfirming.next(false);
this.delete(); // this is the delete action that was passed to the directive
}
}
}
您可以将它应用于这样的元素,将实际的 delete
方法作为参数传递。
<button type="button" [confirm]="delete"></button>
工作示例:https://stackblitz.com/edit/angular-wdfcux
旧答案:
不确定指令是否是最好的方法。它可能会完成,但你必须以某种方式拦截点击处理程序 and/or 将删除方法传递给它。估计会很乱。
我可能会为删除按钮创建一个组件并在那里处理它(好吧,实际上这是一个谎言,如果这是我,我会使用本机 confirm
对话框并完成它,但是你不想)。
像这样:
import { Component } from '@angular/core';
import { Observable, Subject, timer } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'delete-button',
template: `<button type="button" (click)="delete()" [ngClass]="{ delete: isConfirming }">{{ label }}</button>`,
styles: ['.delete { background-color: teal; color: white; } ']
})
export class DeleteButtonComponent {
private confirmTimeout: number = 5000;
private timer$: Observable<number>;
private cancelTimer = new Subject();
public isConfirming: boolean = false;
constructor() {}
get label(): string {
return this.isConfirming
? 'Please confirm delete'
: 'Delete'
}
delete() {
if (!this.isConfirming) {
this.timer$ = timer(this.confirmTimeout);
this.isConfirming = true;
this.timer$
.pipe(
takeUntil(this.cancelTimer)
).subscribe(() => {
this.isConfirming = false;
}, null, () => this.isConfirming = false);
} else {
this.cancelTimer.next();
// really delete
}
}
}