I wasn’t quite sure what to call this post, so instead let’s give give some scenarios in which you want to use a “Texbox with delayed output”
- You want to create your own custom typeahead component that waits until a user stops typing before looking up results
- You want a textbox that waits until a user stops typing, so you can call a backend API to check for duplicated names/emails/records
- You want to add a text filter to a table/list, that only refreshes/filters when you stop typing in a textbox for a short period of time
- In general, we want to use a textbox that waits until the user has stopped typing for a few milliseconds, and only then return the result to us
All of these can be solved using a Textbox with a delayed (or debounced) output. And it’s actually very simple using some helpful code from RxJS.
First I’ll give you the complete code, then point out the important bits :
@Component({
selector: 'app-delayed-textbox',
templateUrl: './delayed-textbox.component.html',
styleUrls: ['./delayed-textbox.component.scss'],
providers : [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DebounceTextboxComponent),
multi: true
}
]
})
export class DelayedTextboxComponent implements OnInit {
delayTime : number = 250
currentText : string;
private textChanged = new Subject();
constructor() { }
ngOnInit(): void {
this.textChanged.pipe(debounceTime(this.delayTime)).subscribe(() => {
this.propogateTouch();
this.propagateChange(this.currentText);
});
}
onTextInput() {
this.textChanged.next();
}
//Start implementation of ControlValueAccessor
propagateChange = (_: any) => {};
propogateTouch = () => {};
writeValue(obj: any): void {
this.currentText = obj;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn : any) {
this.propogateTouch = fn;
}
setDisabledState?(isDisabled: boolean): void {}
//End implementation of ControlValueAccessor
}
This little piece here is where the secret sauce is :
private textChanged = new Subject();
this.textChanged.pipe(debounceTime(this.delayTime)).subscribe(() => {
this.propogateTouch();
this.propagateChange(this.currentText);
});
What this says is as events are pumped into the textChanged subject, “debounce” them. That is, wait until there is a gap of 250ms, then return me the *last* item. If there are multiple events pumped into the subject during this time, still I only want the last one returned to me.
In our view for our custom component, we will have just the following :
<input type="text"
name="delayTextbox"
[(ngModel)]="currentText"
(ngModelChange)="onTextInput()" />
So nothing special, but in particular notice that when the model is changed, onTextInput() is called. This then enqueues a message onto our subject, which then itself debounces for 250ms, and then finally we propogate the change!
We can use this delayed textbox like so :
<app-delayed-textbox
name="myTextbox"
[(ngModel)]="myModel.Field"
(ngModelChange)="onMyModelFieldChanged()">
</app-delayed-textbox >
Notice how because we set up the ControlValueAccessor interface on our custom component, we can simply use the standard ngModel and ngModelChange to be alerted when the field changes.
I’ve seen some really crazy implementations of these sorts of delayed textboxes where people try and time keydowns and keyups, and just a crazy combination of setTimeout and booleans etc. But RxJS essentially does the same thing for us, using the simply debounceTime operator!
💬 Leave a comment