Search code examples
javascriptangularangular-formsangular2-changedetection

Will using Angular Reactive Forms .get() method in template cause unnecessary method calls like a component method?


I know that if I use a method call in a template it will get executed over and over (not ideal). I have solved that by using a combination of pure pipes and memoized methods. But I am also using reactive forms and in my template using the myFormGroup.get('myFormControl').value to get the values. Will this too get executed repeatedly like the method in my component or does Angular have a strategy in place to prevent this? A usage example is using *ngIf and having the conditional be based on a value of the form.

Also, I am not experiencing any performance degradation currently, but I would like to implement this in the best way possible before getting too far down the road with this application (and am just curious).

I can easily update it to reference the properties on the form object directly, I just prefer the syntax of the method call. Any insight would be helpful, thanks!


Solution

  • This is what happens when you call AbstractControl.get(...):

    get(path: Array<string|number>|string): AbstractControl|null {
      return _find(this, path, '.');
    }
    

    Source.

    And the _find function looks like this:

    function _find(control: AbstractControl, path: Array<string|number>|string, delimiter: string) {
      if (path == null) return null;
    
      if (!Array.isArray(path)) {
        path = path.split(delimiter);
      }
      if (Array.isArray(path) && path.length === 0) return null;
    
      // Not using Array.reduce here due to a Chrome 80 bug
      // https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
      let controlToFind: AbstractControl|null = control;
      path.forEach((name: string|number) => {
        if (controlToFind instanceof FormGroup) {
          controlToFind = controlToFind.controls.hasOwnProperty(name as string) ?
              controlToFind.controls[name] :
              null;
        } else if (controlToFind instanceof FormArray) {
          controlToFind = controlToFind.at(<number>name) || null;
        } else {
          controlToFind = null;
        }
      });
      return controlToFind;
    }
    

    Source.

    As you noticed, you can get descendants that reside deeper in the form control tree.

    For instance:

    form.get('a.b.c')
    
    // Or
    
    form.get(['a', 'b', 'c'])
    

    This whole logic involes an iteration, because it's iterating over each element from path.


    Will this too get executed repeatedly like the method in my component

    I'd say it will.

    I've created a StackBlitz demo to illustrate this:

    @Component({
      selector: 'my-app',
      template: `
        <form [formGroup]="form">
          <input formControlName="name" type="text">  
        </form>
    
        <hr>
    
        <p>
          Getter value: {{ name.value }}
        </p>
      `,
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      form: FormGroup;
      name2: FormControl
    
      get name (): FormControl {
        console.log('getter invoked!')
    
        return this.form.get('name') as FormControl;
      }
    
      constructor (private fb: FormBuilder) { }
    
      ngOnInit () {
        this.form = this.fb.group({ name: '' });
    
        this.name2 = this.form.get('name') as FormControl;
      }
    }
    

    If you're using a getter, you should see getter invoked! logged twice for each character you type in the input(and the get method called multiple times as well).

    If you're using {{ form.get('name').value }}, the AbstractControl.get method will be called multiple times, more than expected.

    You can test this by opening the dev tools, typing forms.umd.js and placing a log breakpoint at this line path.forEach(function (name) {...}, inside _find function's body.

    And if you're using this.name2 = this.form.get('name') as FormControl;, you should see nothing logged as you're typing.

    In my opinion, it's less likely that the visible performance will decrease if you're using a getter or .get(), but I'd go with the third approach, creating a separate property for the control I'll be using in the view.