In Angular 19, I need a function to run when a dropdown changes value. However, it doesn't happen when using setValue.
html
<select id="dropdown" [formControl]="dropdown" (change)="changeFunction()">
{{options....}
</select>
ts
ngOnInit(){
this.form.addControl('dropdown', new FormControl('0'))
if(autofill === true)
ddControler.setValue('1') // <<<< DOES NOT RUN changeFunction()
}
get ddControler(): FormControl<string|null> { return this.form.get('dropdown') as FormControl; }
changeFunction(){
console.log('dropdown changed')
}
The change
event is fired only when the user interacts with the page, instead we can use valueChanges
, when you specifically want the event to fire everytime the field is updated.
Here I use pairwise
to provide the old value and new value to perform an equality check to prevent extra calls and takeUntilDestroy
to destroy the listener on component destroy.
ngOnInit() {
this.form.addControl('dropdown', new FormControl('0'));
this.initializeListener();
if (this.autofill) {
this.ddControler.setValue('1');
}
}
initializeListener() {
this.ddControler.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef), pairwise())
// we use pairwise to fire the change event only when the old value differs from the new value
.subscribe((value: any[]) => {
if (value[0] !== value[1]) {
this.changeFunction();
}
});
}
import { Component, DestroyRef, inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { pairwise } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-root',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form">
<select id="dropdown" formControlName="dropdown">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</form>
`,
})
export class App {
destroyRef = inject(DestroyRef);
autofill = true;
form = new FormGroup({});
ngOnInit() {
this.form.addControl('dropdown', new FormControl('0'));
this.initializeListener();
if (this.autofill) {
this.ddControler.setValue('1');
}
}
initializeListener() {
this.ddControler.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef), pairwise())
// we use pairwise to fire the change event only when the old value differs from the new value
.subscribe((value: any[]) => {
if (value[0] !== value[1]) {
this.changeFunction();
}
});
}
get ddControler(): FormControl<string | null> {
return this.form.get('dropdown') as never as FormControl;
}
changeFunction() {
console.log('dropdown changed');
}
}
bootstrapApplication(App);
It is ok to use the change event for simple components, that do not require the change event to fire on patch/set value events.
For those scenarios just call the change event manually on each update.
ngOnInit() {
this.form.addControl('dropdown', new FormControl('0'));
if (this.autofill) {
this.ddControler.setValue('1');
this.changeFunction();
}
}