I'm working with Angular's new Signals feature to manage reactive state in a form.
Everything works fine in most cases. However, there's one issue I'm running into:
When a user refreshes the page while on the form, the component is reloaded. The data that should populate the form is fetched asynchronously and exposed through a Signal
. But the form is initialized before the signal gets its value.
So when I call form.patchValue(...)
during ngOnInit
, the values from the signal are still undefined
.
I also considered using an effect() to reactively update the form when the signal changes, but according to the Angular documentation, effect() is not intended for this kind of logic.
// user userApplication Component
get customer(): Signal<Customer | null> {
return this._store.customer;
}
// form Component
ngOnInit(): void {
const customer = this.userApplication.customer();
if (customer && customer.identity) {
const identity = customer.identity;
this.identity.patchValue({
title: identity.title,
firstName: identity.firstName,
});
}
}
identity = inject(FormBuilder).group({
title: new FormControl<string | null>(null),
firstName: new FormControl<string>('', [Validators.required]),
});
effect
(Single initialization):Use this approach to dynamically sync the signal to the form only once.
I use effectRef.destroy()
to enforce the initialization to happen only once.
The signal can fire anytime, so you need to react to this, one option is to use effect
to patch
the value when the signal is updated.
identity = inject(FormBuilder).group({
title: new FormControl<string | null>(null),
firstName: new FormControl<string>('', [Validators.required]),
});
constructor(): void {
const effectRef = effect(() => {
const customer = this.userApplication.customer();
if (customer && customer.identity) {
const identity = customer.identity;
this.identity.patchValue({
title: identity.title,
firstName: identity.firstName,
});
effectRef?.destroy(); // after patching kill the effect.
}
});
}
computed
( Recompute on each signal change):I have a resource that fetches an entity data, then I take this data and use computed
to patch the values to the form, since the resource is a single initialization, we do not need to worry about recomputation.
This method might not be ideal for your scenario.
Use this approach to dynamically sync the signal to the form with signal values during the initialization.
I am trying out this new approach and it seems to be stable and reliable approach to initialize the angular form.
private formBuilder = inject(FormBuilder);
initializeForm() {
return this.formBuilder.group({
title: new FormControl<string | null>(null),
firstName: new FormControl<string>('', [Validators.required]),
});
}
identityForm = computed(() => {
const identity = this.initializeForm();
const customer = this.userApplication.customer();
if (customer && customer.identity) {
const identity = customer.identity;
identity.patchValue({
title: identity.title,
firstName: identity.firstName,
});
}
return identity;
});
When you use this, do not forget to execute the signal:
<form [formGroup]="identityForm()">
...
</form>