Angular Icon Get 73% off the Angular Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write Angular like a pro. Angular Icon

Follow the ultimate Angular roadmap.

Updating Angular Forms with patchValue or setValue

If you’re still guessing which method to use to update a Reactive Form value in Angular, then this post is for you.

It’s a comprehensive deep dive that will uncover the similarities and differences between patchValue and setValue. I’ve also highlighted key areas of the Angular source code for you whilst explaining the differences. This level of deep knowledge will ensure you’re using the right approach!

Reactive Form Setup

Let’s assume we’re setting up some kind of Event Feedback Form that first accepts our user credentials, followed by the event title and location.

For us to create a new Event Feedback Form is easy, as FormBuilder will initialise specific values, but how would we set a form value should this component also be reused for displaying data already created and stored in the database.

First, assume the following form setup, in real life it would likely involve more form controls to get all the feedback for your particular event, however we’re merely diving into the APIs here to understand how to apply them to anything FormControl related. If you’ve not used FormControl, FormBuilder and friends before I’d highly recommend checking out the aforementioned reactive forms article to understand what’s happening below.

Have a skim of the code and then we’ll progress below.

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { SurveyService } from '../../services/survey.service';

@Component({
  selector: 'event-form',
  template: `
    <form novalidate (ngSubmit)="onSubmit(form)" [formGroup]="form">
      <div>
        <label>
          <span>Full name</span>
          <input type="text" class="input" formControlName="name">
        </label>
        <div formGroupName="event">
          <label>
            <span>Event title</span>
            <input type="text" class="input" formControlName="title">
          </label>
          <label>
            <span>Event location</span>
            <input type="text" class="input" formControlName="location">
          </label>
        </div>
      </div>
      <div>
        <button type="submit" [disabled]="form.invalid">
          Submit
        </button>
      </div>
    </form>
  `,
})
export class EventFormComponent implements OnInit {
  form: FormGroup;
  constructor(
    public fb: FormBuilder,
    private survey: SurveyService
  ) {}
  ngOnInit() {
    this.form = this.fb.group({
      name: ['', Validators.required],
      event: this.fb.group({
        title: ['', Validators.required],
        location: ['', Validators.required]
      })
    });
  }
  onSubmit({ value, valid }) {
    this.survey.saveSurvey(value);
  }
}

The usual suspects are present here, and we’re also introducing the SurveyService to provide the saveSurvey method inside the submit callback. So this is great, however let’s assume we have the following routes:

const routes: Routes = [{
  path: 'event',
  component: EventComponent,
  canActivate: [AuthGuard],
  children: [
    { path: '', redirectTo: 'new', pathMatch: 'full' },
    { path: 'new', component: EventFormComponent },
    { path: 'all', component: EventListComponent },
    { path: ':id', component: EventFormComponent },
  ]
}];

Specifically, the child route of /event contains this:

{ path: ':id', component: EventFormComponent }

This will allow us to essentially achieve a URL such as this (with a unique id hash):

localhost:4200/event/-KWihhw-f1kw-ULPG1ei

If you’ve used firebase before these keys will likely look somewhat familar. So let’s assume we just hit the above route, and want to update the form’s value. This can be done with a route resolve, however for these purposes - we’re not going to use one as we’ll be using an observable which will allow us to subscribe to route param changes and fetch new data and render it out.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

So let’s introduce the router code to the initial component. First we’ll import this:

import 'rxjs/add/operator/switchMap';
import { Observable } from 'rxjs/Observable';

import { Router, ActivatedRoute, Params } from '@angular/router';

We’re importing Observable and adding switchMap to ensure it’s available. From here we can inject the ActivatedRoute inside the constructor:

constructor(
  public fb: FormBuilder,
  private survey: SurveyService,
  private route: ActivatedRoute
) {}

Now we can jump back inside ngOnInit and add a subscription:

ngOnInit() {
  this.form = this.fb.group({
    name: ['', Validators.required],
    event: this.fb.group({
      title: ['', Validators.required],
      location: ['', Validators.required]
    })
  });
  this.route.params
    .switchMap((params: Params) => this.survey.getSurvey(params['id']))
    .subscribe((survey: any) => {
      // update the form controls
    });
}

So anytime the route params change, we can use our getSurvey method, pass in the current param in the URL (the unique :id) and go fetch that unique Object. In this case, I’ve been using AngularFire2 which returns a FirebaseObjectObservable, therefore I can pipe it through switchMap and get the data through the subscribe.

The next question: patchValue or setValue? Before using an API I’ve gotten into the good habit of looking through the source code, so let’s quickly run over the difference between the two:

patchValue

We’ll start with patchValue and then move onto setValue. Firstly “patch” sounds a bit off-putting, like it’s an API name that I shouldn’t really be using - but that’s not the case! Using patchValue has some benefits over setValue, and vice versa. These will become apparent after digging into the source…

There are actually two things happening when updating a FormGroup versus FormControl, as patchValue has two implementations which we’ll look at below

So, the source code for the FormGroup implementation:

patchValue(value: {[key: string]: any}, {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
  Object.keys(value).forEach(name => {
    if (this.controls[name]) {
      this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent});
    }
  });
  this.updateValueAndValidity({onlySelf, emitEvent});
}

All this patchValue really is, is just a wrapper to loop child controls and invoke the actual patchValue method. This is really the piece you need to be interested in:

Object.keys(value).forEach(name => {
  if (this.controls[name]) {
    this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent});
  }
});

Firstly, Object.keys() will return a new Array collection of Object keys, for example:

const value = { name: 'Todd Motto', age: 26 };
Object.keys(value); // ['name', 'age']

The forEach block that follows simply iterates over the FormGroup keys and does a hash lookup using the name (each string key) as a reference inside the current FormGroup instance’s controls property. If it exists, it will then call .patchValue() on the current this.controls[name], which you might be wondering how does it call patchValue on a single control as we’re actually calling it from the FormGroup level. It’s just a wrapper to loop and invoke model updates the child FormControl instances.

Let’s loop back around before we get lost to understand the cycle here. Assume our initial FormGroup:

this.form = this.fb.group({
  name: ['', Validators.required],
  event: this.fb.group({
    title: ['', Validators.required],
    location: ['', Validators.required]
  })
});

All we have here really in Object representation is:

{
  name: '',
  event: {
    title: '',
    location: ''
  }
}

So to update these model values we can reference our FormGroup instance, this.form and use patchValue() with some data:

this.form.patchValue({
  name: 'Todd Motto',
  event: {
    title: 'AngularCamp 2016',
    location: 'Barcelona, Spain'
  }
});

This will then perform the above loop, and update our FormControl instances, simple!

So, now we’re caught up on the full cycle let’s look at the FormControl specific implementation:

patchValue(value: any, options: {
  onlySelf?: boolean,
  emitEvent?: boolean,
  emitModelToViewChange?: boolean,
  emitViewToModelChange?: boolean
} = {}): void {
  this.setValue(value, options);
}

Ignoring all the function arguments and types, all it does is call setValue, which - sets the value.

So, why use patchValue? I came across the use case for this when I was also using firebase. I actually get $exists() {} and $key returned as public Object properties from the API response, to which when I pass this straight from the API, patchValue throws no error:

this.form.patchValue({
  $exists: function () {},
  $key: '-KWihhw-f1kw-ULPG1ei',
  name: 'Todd Motto',
  event: {
    title: 'AngularCamp 2016',
    location: 'Barcelona, Spain'
  }
});

It throws no errors due to the if check inside the Object.keys loop. Some might say it’s a safe $apply, just kidding. It’ll allow you to set values that exist and it will ignore ones that do not exist in the current iterated control.

setValue

So now we’ve checked patchValue, we’ll look into setValue. You may have guessed by now, that it’s a “more safe” way to do things. It’ll error for props that do not exist.

The FormGroup implementation for setValue:

setValue(value: {[key: string]: any}, {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
  this._checkAllValuesPresent(value);
  Object.keys(value).forEach(name => {
    this._throwIfControlMissing(name);
    this.controls[name].setValue(value[name], {onlySelf: true, emitEvent});
  });
  this.updateValueAndValidity({onlySelf, emitEvent});
}

Just like before, we have the Object.keys iteration, however before the loop the values are all checked a _checkAllValuesPresent method is called:

_checkAllValuesPresent(value: any): void {
  this._forEachChild((control: AbstractControl, name: string) =&gt; {
    if (value[name] === undefined) {
      throw new Error(`Must supply a value for form control with name: '${name}'.`);
    }
  });
}

This just iterates over each child control and ensures that the name also exists on the Object by a lookup with value[name]. If the control value does not exist on the Object you’re trying to setValue, it will throw an error.

Providing your FormControl exists, Angular moves onto the Object.keys loop, however will first check that the control is missing for that value also via _throwIfControlMissing:

_throwIfControlMissing(name: string): void {
  if (!Object.keys(this.controls).length) {
    throw new Error(`
      There are no form controls registered with this group yet.  If you're using ngModel,
      you may want to check next tick (e.g. use setTimeout).
    `);
  }
  if (!this.controls[name]) {
    throw new Error(`Cannot find form control with name: ${name}.`);
  }
}

First it’ll check if the this.controls even exists, and then it’ll ensure - i.e. the FormControl instances inside FormGroup - and then it’ll check if the name passed in even exists on the said FormControl. If it doesn’t - you’re getting an error thrown at you.

If you’ve reached this far, the following gets invoked and your value is set:

this.controls[name].setValue(value[name], {onlySelf: true, emitEvent});

Finally, we’ll check the source code of the individual FormControl’s implementation of setValue:

setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange}: {
  onlySelf?: boolean,
  emitEvent?: boolean,
  emitModelToViewChange?: boolean,
  emitViewToModelChange?: boolean
} = {}): void {
  this._value = value;
  if (this._onChange.length && emitModelToViewChange !== false) {
    this._onChange.forEach((changeFn) => changeFn(this._value, emitViewToModelChange !== false));
  }
  this.updateValueAndValidity({onlySelf, emitEvent});
}

This function alone doesn’t tell you anything of what’s happening internally as the changeFn are dependent from elsewhere, depending on what code is using the setValue internally. For instance, here’s how a changeFn gets set via a public method (note the .push(fn) being the changeFn):

registerOnChange(fn: Function): void { this._onChange.push(fn); }

This will be from various other places from within the source code.

Looping back round again to updating our FormGroup, we can make a quick setValue call like so:

this.form.setValue({
  name: 'Todd Motto',
  event: {
    title: 'AngularCamp 2016',
    location: 'Barcelona, Spain'
  }
});

This would then update the this.form perfectly without errors, however when we invoke this next piece, the errors are thrown:

this.form.setValue({
  $exists: function () {},
  $key: '-KWihhw-f1kw-ULPG1ei',
  name: 'Todd Motto',
  event: {
    title: 'AngularCamp 2016',
    location: 'Barcelona, Spain'
  }
});

Hopefully this answered a few questions on the differences between the two implementations.

FormControl patchValue / setValue

By diving through the source code we’ve also learned that you can call these methods directly to update particular FormControl instances, for example:

this.survey.controls['account'].patchValue(survey.account);
this.survey.controls['account'].setValue(survey.account);

These are in the Angular docs, but the source code often makes more sense of what’s really happening.

Source code

If you’d like to dig through the source code yourself, check it out here.

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover