CNI*_*NIS 0 html input rxjs typescript angular
我有以下输入:
<input type="text" placeholder="Search for new results" (input)="constructNewGrid($event)" (keydown.backslash)="constructNewGrid($event)">
Run Code Online (Sandbox Code Playgroud)
和功能
constructNewGrid(e){
// I want to wait 300ms after the last keystroke before constructing the new grid
// If the passed time is <300ms just return without doing something
// else start constructing new grid
}
Run Code Online (Sandbox Code Playgroud)
我不太确定如何建立这样的条件。我应该如何解决这个问题?我在 RxJS 中阅读了关于 debounceTime 的内容,这正是我想要的,但我没有在函数中使用 observable,所以:你将如何在函数中构建这样的条件?
Observables 似乎是要走的路,但好的老人setTimeout也会让你走很长的路。出于美观原因,让我们首先重命名您的输入处理程序:
反斜杠事件似乎有点双重,因为这也会触发 (input)
<input type="text" placeholder="Search for new results"
(input)="onInput(input.value)" #input>
Run Code Online (Sandbox Code Playgroud)
在您的组件中,您有两种选择来处理此输入,使用 observable 或不使用。让我先展示给你看:
export class GridComponent {
private timeout?: number;
onInput(value: string): void {
window.clearTimeout(this.timeout);
this.timeout = window.setTimeout(() => this.constructNewGrid(value), 300);
}
constructNewGrid(value: string): void {
// expensive operations
}
}
Run Code Online (Sandbox Code Playgroud)
这看起来很简单,对于您的用例来说可能就足够了。但是人们一直在谈论的那些很酷的 rxjs 流呢?看起来像这样:
export class GridComponent {
private search$ = new BehaviorSubject('');
private destroy$ = new Subject<void>();
ngOnInit(): void {
this.search$.pipe(
// debounce for 300ms
debounceTime(300),
// only emit if the value has actually changed
distinctUntilChanged(),
// unsubscribe when the provided observable emits (clean up)
takeUntil(this.destroy$)
).subscribe((search) => this.constructNewGrid(search));
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
onInput(value: string): void {
this.search$.next(value);
}
constructNewGrid(value: string): void {
// expensive operations
}
}
Run Code Online (Sandbox Code Playgroud)
对于这样一个简单的事情,这看起来像是更多的代码,而且确实如此。所以这取决于你。
但是,如果您觉得这种模式是您将更经常使用的东西,您也可以考虑编写一个指令,它看起来像这样:
@Directive({
selector: '[debounceInput]'
})
export class DebounceInputDirective {
@Input()
debounceTime: number = 0;
@HostListener('input', '[$event]')
onInput(event: UIEvent): void {
this.value$.next((event.target as HTMLInputElement).value);
}
private value$ = new Subject<string>();
@Output()
readonly debounceInput = this.value$.pipe(
debounce(() => timer(this.debounceTime || 0)),
distinctUntilChanged()
);
}
Run Code Online (Sandbox Code Playgroud)
您可以像这样在组件中使用它:
<input type="text" placeholder="Search for new result"
(debounceInput)="onInput($event)" [debounceTime]="300">
Run Code Online (Sandbox Code Playgroud)
以更加 rxjs 风格编写此指令的另一种方法是:
@Directive({
selector: 'input[debounceInput]'
})
export class DebounceInputDirective {
@Input()
debounceTime: number = 0;
constructor(private el: ElementRef<HTMLInputElement>) {}
@Output()
readonly debounceInput = fromEvent(this.el.nativeElement, 'input').pipe(
debounce(() => timer(this.debounceTime)),
map(() => this.el.nativeElement.value),
distinctUntilChanged()
);
}
Run Code Online (Sandbox Code Playgroud)
using 指令(和不相关的async管道)的好处是您不必担心挥之不去的 rxjs 订阅。这些可能是潜在的内存泄漏。
可是等等!还有更多。您可以忘记所有这些事情,并使用 angular 回到打字稿的根源。装饰者!在你的方法上使用一个花哨的去抖动装饰器怎么样。然后你可以保留之前的所有内容,只需@debounce(300)在你的方法上方添加:
@debounce(300)
constructNewGrid(event): void {
// ...
}
Run Code Online (Sandbox Code Playgroud)
什么?真的吗?这个 debounce 装饰器是什么样子的。好吧,它可以像这样简单:
function debounce(debounceTime: number) {
let timeout: number;
return function (
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod: Function = descriptor.value;
descriptor.value = (...args: any[]) => {
window.clearTimeout(timeout);
timeout = window.setTimeout(() => originalMethod(...args), debounceTime);
};
return descriptor;
};
}
Run Code Online (Sandbox Code Playgroud)
但这是未经测试的代码,但它是为了让您了解所有可能的内容:)