How to fire the `valueChanges` programmatically?
On one of my pages I use a FormBuilder
to fill a form at initialization. Every input gets a class whenever the input is not empty. This is done by adding a ngClass
to every input, and subscribing on the FormGroup
's valueChanges
.
My problem occurs whenever the form is filled programmatically. Whenever a user changes any value on the form, valueChanges
gets called, however, this is not the case when using a FormBuilder
.
My question is: How to get the valueChanges
event to be fired whenever the FormBuilder
is finished.
I have tried using this.FormGroup.updateValueAndValidity()
, but this did not result in anything.
Solution 1:
I found a solution which worked for me. I forgot two parameters in the updateValueAndValidity
function.
- onlySelf: will only update this
FormControl
when true. - emitEvent: will cause
valueChanges
event to be fired when true.
So as result you will get something like:
FormGroup.updateValueAndValidity({ onlySelf: false, emitEvent: true });
Solution 2:
Your question isn't 100% clear, but I think what you're saying it :
My valueChanges works fine when changing something in the UI, but I want to trigger my subscribe function logic as soon as I have finished initializing the FormBuilder object in my constructor to handle the initial conditions.
In that case what I do is quite simple:
this.searchForm.valueChanges
.pipe(startWith(initialValues)).subscribe(value =>
{
// whatever you want to do here
});
initialValues
is the raw data you've initialized the form with. You could probably just put in searchForm.getRawValue()
too.
This just causes the observable to fire immediately.
Important: There is a subtle problem with the original answer if you're using the async pipe to subscribe instead of an explicit subscribe()
.
In Angular you can subscribe explicitly to an observable (as shown above), but it's equally common to use the async pipe. An example looks like this:
model = {
firstName: this.searchForm.valueChanges.pipe(startWith(initialValues), map(values => values.firstName.toUpperCase())
};
and then in your template display the form value:
First name: {{ model.firstName | async | json }}
There is a problem with this approach that is quite subtle.
- The
startWith
operator will capture the value when you create the observable, which will be correct the first time. - However if you re-subscribe to the observable it still be using the original values even if the form has changed. This can manifest itself if the UI is hidden and then displayed again (causing a new subscription).
Possible solutions:
-
Use
defer
such that the value ofthis.searchForm.value
will be evaluated each time the observable is subscribed to.defer(() => this.searchForm.valueChanges.pipe(startWith(this.searchForm.value), map(values => values.firstName.toUpperCase())
-
Use
shareReplay
. This only works here withrefCount: false
and therefore I do not recommend it since it won't get cleaned up properly.this.searchForm.valueChanges.pipe(startWith(initialValues), map(values => values.firstName.toUpperCase(), shareReplay({ refCount: false, bufferSize: 1 })
-
Hypothetical: If
startWith
had a lambda then you could use that. Unfortunately it doesn't right now:startWith(() => this.searchForm.value)
Note: If you're using subscribe
explicitly then you don't need to worry about this issue.