Currently, I am developing an Angular application with a specific scenario. I have an observable signal named dataFetchedEvent$
, which indicates that data has been fetched from a remote location. Additionally, there is a form that relies on this remote data and listens to user changes through the formChanges$
.
const dataFetchedEvent$ = this.event.asObservable()
.pipe(
tap((fetchedData) => {
this.createFormControls(fetchedData); // builds reactive form controls based on the fetched data
}),
takeUntil(this.componentDestroyed) // triggered in ngOnDestroy of the component
);
const formChanges$ = this.form.valueChanges // listening to form changes
.pipe(
switchMap((formDataRaw) => this.formatData(formDataRaw)), // transforming data
takeUntil(this.componentDestroyed)
);
dataFetchedEvent$.subscribe();
formChanges$.subscribe(
(formattedFormData) => {
// perform actions...
}
);
All functions correctly; however, when dataFetchedEvent$
is activated, the formChanges$
subscription triggers every time a control is altered by
this.createFormControls(fetchedData)
. I aim to prevent this behavior and only monitor form changes after the controls are created or updated.
I've explored various solutions:
Using skipUntil:
const formChanges$ = this.form.valueChanges // listening to form changes
.pipe(
skipUntil(dataFetchedEvent$),
switchMap((formDataRaw) => this.formatData(formDataRaw)), // transforming data
takeUntil(this.componentDestroyed)
);
Although this approach stops multiple emissions of formChanges$
while creating controls and works during component destruction and recreation, it unsubscribes dataFetchedEvent$
after initial emission. This causes issues as dataFetchedEvent$
may need to emit again to update form controls.
Utilizing concat:
const formChanges$ = this.form.valueChanges // listening to form changes
.pipe(
switchMap((formDataRaw) => this.formatData(formDataRaw)), // transforming data
tap((formattedFormData) => {
// carry out operations...
}),
takeUntil(this.componentDestroyed)
);
concat(dataFetchedEvent$, formChanges$).subscribe();
In this instance, the formChanges$
stream remains unresponsive because dataFetchedEvent$
doesn't complete.
Exploring combineLatest:
combineLatest(dataFetchedEvent$, formChanges$).subscribe([val1, val2] => { // perform tasks... });
This method isn't ideal as formChanges$
still emits while creating controls.
Experimenting with mergeMap or switchMap:
const dataFetchedEvent$ = this.event.asObservable()
.pipe(
tap((fetchedData) => {
this.createFormControls(fetchedData); // creates reactive form control based on remotely fetched data
}),
mergeMap(() => formChanges$),
takeUntil(this.componentDestroyed) // triggered in ngOnDestroy of the component
);
dataFetchedEvent$.subscribe();
The above solution works initially or during component destruction and recreation. However, if dataFetchedEvent$
emits again after the first call, formChanges$
emits whenever form controls are updated, even with switchMap
.
What would be the most effective strategy to achieve my objective?
------ EDIT ------
In the previous example, the code was generic and simplified.
Here are the form controls:
form = this.fb.group({
tourLength: this.fb.group({
length: [false]
}),
types: this.fb.group({
type: [false]
}),
categories: this.fb.group({}),
languages: this.fb.group({
lang: [false]
}),
prices: this.fb.group({
opt: [false]
})
});
The only dynamically changing group is 'categories'. When dataFetchedEvent$
emits, it means new categories were fetched remotely, prompting me to update the controls using _createControlsCategories()
(essentially the prior createFormControls
function).
private _createControlsCategories(categories) {
const categoriesGroup: FormGroup = this.filterForm.get('categories') as FormGroup;
categoriesGroup.controls = {};
for (const cat of categories) {
categoriesGroup.addControl(cat.id_remote, new FormControl(false));
}
}
Each category is linked to a checkbox in the view.