I would like to initialize a Template-Driven Form with query parameter values.
Intuitively you would create the form and populate it on ngAfterViewInit:
HTML
<form #f="ngForm">
<input type="text" id="firstName" name="fname" #fname ngModel>
<input *ngIf="fname.value" type="text" id="lastName" name="lname" ngModel>
<button type="submit">Submit</button>
</form>
Component:
@ViewChild('f') f: NgForm;
constructor(private route: ActivatedRoute) {}
ngAfterViewInit() {
const queryParams = this.route.snapshot.queryParams;
this.f.form.setValue(queryParams)
}
then access it with query parameters: ?fname=aaa&lname=bbb
now, there are two issues with this approach:
- as it turns out, this does not work because Angular requires another tick to register the form
setValuewon't work because the second ctrl,lnamedoesnt exist at the time of applying the values.
this will require me to
- add an extra cycle (Angular team suggests setTimeout @ console error)
- use
patchValuethat only applies valid values, twice.
something like:
ngAfterViewInit() {
const queryParams = { fname: 'aaa', lname: 'bbb'};
// if we wish to access template driven form, we need to wait an extra tick for form registration.
// angular suggests using setTimeout or such - switched it to timer operator instead.
timer(1)
// since last name ctrl is only shown when first name has value (*ngIf="fname.value"),
// patchValue won't patch it on the first 'run' because it doesnt exist yet.
// so we need to do it twice.
.pipe(repeat(2))
// we use patchValue and not setValue because of the above reason.
// setValue applies the whole value, while patch only applies controls that exists on the form.
// and since, last name doesnt exist at first, it requires us to use patch. twice.
.subscribe(() => this.f.form.patchValue(queryParams))
}
Is there a less hacky way to accomplish this Without creating a variable for each control on the component side as doing that, would, in my opinion, make template driven redundant.
attached: stackblitz Demo of the "hacky" soultion