Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize template driven Form with query parameters

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:

  1. as it turns out, this does not work because Angular requires another tick to register the form
  2. setValue won't work because the second ctrl, lname doesnt exist at the time of applying the values.

this will require me to

  1. add an extra cycle (Angular team suggests setTimeout @ console error)
  2. use patchValue that 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

like image 633
Stavm Avatar asked Oct 16 '22 01:10

Stavm


1 Answers

with [(ngModel)] can try the below

<form #heroForm="ngForm">
<div class="form-group">
    <label for="fname">First Name</label>
    <input type="text" class="form-control" name="fname" [(ngModel)]="queryParams.fname" required>
</div>
    <div class="form-group" *ngIf="queryParams?.fname">
        <label for="lname">Last Name</label>
        <input type="text" class="form-control" name="lname" [(ngModel)]="queryParams.lname">
</div>
        <button type="submit" class="btn btn-success">Submit</button>

Then in form component

export class HeroFormComponent implements OnInit {
  @ViewChild("heroForm", null) heroForm: NgForm;
queryParams={};
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    
    this.queryParams = { fname: "aaa", lname: "bbb" };
  }
}

You no need to declare for each form control. just assign queryParams & ngModel will handle the rest.

like image 196
Iam Coder Avatar answered Oct 19 '22 01:10

Iam Coder