Reactive Forms - aakash14goplani/FullStack GitHub Wiki


Creating Form in Code

  • I will create a new property which will hold my form in the end and name it signupForm which will be of type FormGroup in ngOnInit().

  • In a template driven approach, we had ngForm that was automatically creating wrapper for form similarly for Reactive approach FormGroup creates the wrapper and provides a group of controls. Therefore, the overall form also is just a form group.

  • You should initialize property before rendering the template, since property is of type FormGroup, default initialization should also be of FormGroup

    signupForm: FormGroup;
    ngOnInit() {
      this.signupForm = new FormGroup({ });
    }
  • This is JavaScript object and we can configure it to have controls. Controls are basically just key-value pairs in this object we pass to the overall FormGroup. Controls are set using FormControl

    this.signupForm = new FormGroup({ 
       username: new FormControl(null),
       email: new FormControl(null),
       gender: new FormControl('male')
    });
  • In FormControl constructor, we can pass a couple of arguments,

    • the first argument is the initial state, the initial/default value of this control.
    • the second argument will be a single validator or an array of validators we want to apply to this control.
    • the third argument will be potential asynchronous validators.
  • For now, I want to set an initial state for username and email to null to have an empty field but you could also pass a string like default username. For gender, lets set the default values as 'male'.


Sync HTML and Form

  • Actual form is in the HTML template, so we somehow need to synchronize our HTML inputs and our own form. Right now, Angular doesn't know which of our TypeScript controls here relates to which input in our template code, it actually doesn't even know that our form, signupForm here should be attached to this form.

  • Right now it is auto-detecting that this is a form and it creates a form for us. We don't want it to do that, so we have to add some directives to overwrite this default behavior to give Angular different instructions.

  • The first directive we need to add via property binding is the formGroup directive. This simply tells Angular, hey please take my formGroup, don't infer one, don't create a form for me, use my formGroup and we need to set up property binding here because we need to pass our form as an argument to the directive. So here we should reference our signupForm, the property we created here which stores our form. We're passing this via property binding to the formGroup.

    <form [formGroup]="signupForm">
  • Now this form is actually synchronized with the form we created in TypeScript but we still need to tell Angular which controls should be connected to which inputs in the template code, for this we get another directive.

  • On this input, for example, the username, we add the formControlName directive to tell Angular, hey the name of this input in my TypeScript form is username, and that's the control I want to connect to this input, so I simply pass username here.

    <input type="text" id="username" formControlName="username" class="form-control">
  • If you're wondering why I'm not using property binding, I'm passing a string here. So if you want to use property binding, you can do this by wrapping this in square brackets and then enclosing the username in single quotation marks otherwise it would search for a property named username but this is overly complicated, if you just want to pass a string, simply omit the square brackets and you're good to go.

    <input type="text" id="username" [formControlName]="'username'" class="form-control">
  • So with this we're telling Angular hey my form should be connected to the form stored in the signupForm property and in this form, this input here should be connected to the control with the name username.


Submitting Form

  • In the template driven approach, we used ngSubmit() directive on form element. We still do the same here because we still want to react to this default submit event which is fired by HTML, by JavaScript. So we still add ngSubmit() here and we could execute an onSubmit() method.

    <form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
    onSubmit(): void {
     console.log('signupForm: ', this.signupForm);
    }
  • The difference to the template driven approach is that we don't need to get the form via local reference, that actually wouldn't work anymore because we're not using Angular's auto-creation mechanism and we don't need to get this reference because we created the form on our own, we already got access to it here in our TypeScript code,

  • This is the cool thing about the reactive approach, whatever you set up here as an object you pass to the FormGroup which makes up your form, that is what you get out as a value of the form. So you can bind it to your own model of your application and easily make sure that the form structure matches the structure of your model.

  • This is how you can submit the form, you can still access the value as you did before (using value property) but now using your own form, the form you created in TypeScript.


Adding Validations

  • In the template driven approach, we would simply add required attribute to make this field required. In the reactive approach, it doesn't work like this because you're not configuring the form in the template, you're only synchronizing it with the directives formControlName and formGroup, but you're configuring it in the TypeScript code.

  • That is why FormControl takes more than one argument in the constructor that allows you to specify some validators. So you can either only pass one validator or multiple validators in Array of Validators. Here validators is build-in object imported from @angular/forms which has couple of most frequently used validators.

    this.signupForm = new FormGroup({
       username: new FormControl(null, Validators.required),
       email: new FormControl(null, [Validators.required, Validators.email]),
       gender: new FormControl('male')
     });
  • Note: Make sure to not call it, to not execute it, (i.e. Validators.required()) because you don't want to execute this method, it is a static method made available by validators, you only want to pass the reference to this method. Angular will execute the method whenever it detects that the input of this FormControl changed, it just needs to have a reference on what it should execute at this point of time.


Getting access to controls

  • As in a template driven approach, we can now use this form status to display helper/error messages but it works a bit differently because we access the controls differently.

  • In the template driven approach, we would use NgModel to get the reference but this doesn't work here because form is not set up via NgModel.

  • Here we have a get() method that allows us to get access to our controls easily, here you can either specify the control name (e.g. username) or the path to the control (e.g. objA.objB.username).

  • Along with get() method we use CSS classes ng-touched, ng-invalid etc to display error/helper messages:

    <span *ngIf="!signupForm.get('username').valid && signupForm.get('username').touched" class="help-block">
       Please enter valid username!
    </span>
    input.ng-invalid.ng-touched {
       border: 1px solid red;
    }

Group Form Controls

  • You can specify a path here (username => objA.username) because you might have a nested form. Let's say username and e-mail should be inside a FormGroup, so here, we could create a FormGroup named userData. FormGroup is not only there to be used on the overall form, you can still have form groups in the form groups.

    this.signupForm = new FormGroup({
       username: new FormControl(null, Validators.required),
       email: new FormControl(null, [Validators.required, Validators.email]),
       gender: new FormControl('male')
     });

    CHANGES TO...

    this.signupForm = new FormGroup({
       userData: new FormGroup({
         username: new FormControl(null, Validators.required),
         email: new FormControl(null, [Validators.required, Validators.email])
       }),
       gender: new FormControl('male')
     });
  • We need to reflect this in our HTML template for that we need to update our synchronization and we easily do this by adding the formGroupName directive. Here, the formGroupName is userData. One last change - We need to update get to point to the path or to contain the path to that username and that would be userData.username.

    <div formGroupName="userData">
       <div class="form-group">
          <label for="username">Username</label>
          <input type="text" id="username" formControlName="username" class="form-control">
          <span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched" class="help-block">
             Please enter valid username
          </span>
       </div>
       <div class="form-group">
          <label for="email">Email</label>
          <input type="text" id="email" [formControlName]="'email'" class="form-control">
          <span *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched" class="help-block">
             Please enter valid email
          </span>
       </div>
    </div>

Array of Form Control

Allowing user to add dynamic input fields

Use Case: When the user clicks the button, I want to dynamically add a control to my form.

  • The new field should be synonym to an array, for this we could use FormArray. FormArray holds an array of controls, so you pass an array here to initialize it. In this array, you could already initialize some form controls with new FormControl or leave it empty to not have any hobbies at the beginning.
this.signupForm = new FormGroup({
   ...
   hobbies: new FormArray([])
});
<div formArrayName="hobbies">
   <h4>Your Hobbies</h4>
   <button class="btn btn-default" (click)="onHobby()" type="button">Add Hobby</button>
   <div class="form-group">
      <!-- replicate this on every button click -->
      <input type="text" class="form-control">
   </div>
</div>
  • Now when we click onAddHobby(), I want to add a new hobby to that array. For that I need to access my form and I need to tell TypeScript that this is of type FormArray to not get an error and now I can push a new control on this array, if we would have not casted this, we would get an error.

    onHobby(): void {
     const hobbyControl = new FormControl(null, Validators.required);
     (this.signupForm.get('hobbies') as FormArray).push(hobbyControl);
    }
  • We've created this with no default value, you could also change this behavior to pass an argument to onAddHobby() and then pre-populate it in place of null.

  • We can also add validators. Finally we need to synchronize it with our HTML code. For this add a directive, formArrayName - this tells Angular that somewhere in this div, our array will live.

  • Now I now somehow need to loop through all the controls which are in this array. I will add an ngFor loop to loop through all my hobby controls and I also want to extract the index of the current iteration, I will need this to assign this input to one of these dynamically created controls because on this input I want to add the form-controls CSS class and very important, I need to add formControlName because we still need to synchronize this input with the dynamically created input.

  • Now this dynamically created input will not have a name chosen by us but it is an array, so the name will simply be the index in this array, which is why I'm retrieving it here. So I can simply bind formControlName

    <div class="form-group" *ngFor="let hobbyControl of controls; let i = index">
        <input type="text" class="form-control" [formControlName]="i">
    </div>

Note

In the following code:

*ngFor="let hobbyControl of signupForm.get('hobbies').controls; let i = index"

This code will fail as of the latest Angular version. You can fix it easily though. Outsource the "get the controls" logic into a method of your component code (the .ts file):

getControls() {
  return (<FormArray>this.signupForm.get('hobbies')).controls;
}

In the template, you can then use:

*ngFor="let hobbyControl of getControls(); let i = index"

Alternatively, you can set up a getter and use an alternative type casting syntax:

get controls() {
  return (this.signupForm.get('hobbies') as FormArray).controls;
}

and then in the template:

*ngFor="let hobbyControl of controls; let i = index"

This adjustment is required due to the way TS works and Angular parses your templates (it doesn't understand TS there).


Custom Validators

  • Use Case: We have some usernames we don't want to allow the user to use.

  • So now I want to create my own validator which checks whether the username the user entered is one of the two usernames it specified here in the array:

    forbiddenUserNames: string[] = ['Aakash', 'Goplani'];
  • A validator in the end is just a function which gets executed by Angular automatically when it checks the validity of the FormControl and it checks that validity whenever you change that control.

  • Now for a validator to work correctly needs to receive an argument which is the control it should check, so this will be of type FormControl, a validator also needs to return something for Angular to be able to handle the return value correctly, this will be a JavaScript object having any key which can be interpreted as a string and value that would be a boolean.

    forbiddenNames(control: FormControl): {[s: string]: boolean} { ... }
  • So this function here should return something like let's say an object where we have name is forbidden, this would be the key name which is interpreted as a string and it could be true.

    return {'nameIsForbidden': true};
  • If the username is forbidden, I want to return an object where I say name is forbidden, any short error code you want, is true. Now in the other case, I want to return null, if validation is successful, you have to pass null, you should not pass this object with false. This might sound counter-intuitive but that's just how it works, it should be null or you simply omit the return statement.

    forbiddenNames(control: FormControl): {[s: string]: boolean} {
      if (this.forbiddenUserNames.indexOf(control.value) !== -1) {
        return {'nameIsForbidden': true};
      }
      return null;
    }
  • Now want to assign this to username validators, so I'll change this appropriately and I will now add a reference to my forbiddenNames() function again, don't execute it, only pass a reference (Angular will execute this)

    username: new FormControl(null, [Validators.required, this.forbiddenNames])
  • Now if we save this, we get an error. Now this can be a tough one to spot, what's going wrong here? This error has something to do with the way JavaScript handles this.

    • In forbiddenNames(), everything might look all right because I'm in this class and I access this forbiddenUsernames but think about who is calling these forbiddenNames().
    • We're not calling it from inside this class, Angular will call it when it checks the validity, at this point of time, this will not refer to our class here.
    • So to fix this, I actually need to bind this, the good old JavaScript trick to make sure that this refers to what we want it to refer to.
    username: new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)])

Accessing Error Codes

  • To access the error code (nameIsForbidden) -

  • Let's say we want to say the username is required if the field is empty and we want to say this is an invalid username if it is invalid. Also if form has nameIsForbidden code, it is invalid.

    <span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched" class="help-block">
       <span *ngIf="signupForm.get('userData.username').errors['nameIsForbidden']" class="help-block">Invalid username!</span>
       <span *ngIf="signupForm.get('userData.username').errors['required']" class="help-block">Please enter valid username</span>
    </span>
  • This is how we can use these error codes and of course you could also use ngSwitch here or any other set up. The key thing is to understand that these error codes can be used to show the right error messages!

Asynchronous Validators

  • Use Case: Validator should process the input and return the response if it is valid or not

  • It may take some time for validator to process input data, meanwhile we cannot hangup on our customers and make them wait till status is displayed so we have to create an asynchronous validator.

  • Let's create one for email validation, I'll name it forbiddenEmails

    • This asynchronous validator takes the control as an argument.
    • We also need to return something here but this will not be an object with an error code and a boolean, instead this will be a promise which wraps anything or an observable which wraps anything.
    forbiddenEmails(control: FormControl): Promise<any> | Observable<any> { ... }
  • Just to mimic that response in coming from server, in promise function, I now want to set a timeout, after few seconds I one to return a response and simulate asynchronous behavior.

  • If validation fails and as in the synchronous validator case, this is when I will return an object with a key-value pair with this error code i.e. emailIsForbidden and set this to true.

  • If validation pass, so in that case that we have a valid input, that we have a valid e-mail address, I will simply resolve null and return this promise in the end.

    forbiddenEmails(control: FormControl): Promise<any> | Observable<any> {
      const promise = new Promise<any>((resolve, reject) => {
        setTimeout(() => {
          if (control.value === '[email protected]') {
            resolve({'emailIsForbidden': true});
          } else {
            resolve(null);
          }
        }, 2000);
      });
      return promise;
    }
  • Now we can add it to email validator, it is bit different from synchronous validators, here we make use of the third argument, this is an asynchronous validator or an array of such validators, just like the normal validators but reserved for the asynchronous ones.

    • (again don't execute it, simply pass the reference and you need to bind this if you plan on using that in synchronous but since we are working with asynchronous argument, we can skip that)
    email: new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmails)

Accessing Error Codes for Asynchronous Validators

  • With Asynchronous Validators, the status of this input switches from invalid to pending to valid for example.

  • To track each state, you can listen to Observables, since Asynchronous Validators returns Observables. You have two observables you can listen to:

    • statusChanges - this will be the status (valid/invalid/pending) of input field we're listening to.
    • valueChanges - this will be the value of input field we're listening to.
  • For both these Observables, If I type something in input field, with every keystroke, this Observable will emit value

    this.signupForm.get('userData.email').valueChanges.subscribe(
       (value) => {
         console.log('email value changed: ', value);
       }
     );
    
     this.signupForm.get('userData.email').statusChanges.subscribe(
       (status) => {
         console.log('email status changed: ', status);
       }
     );
    <span class="help-block-pending" *ngIf="signupForm.controls.userData.controls.email.pending">
       Checking Email Validity...
    </span>
    <span *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched && !signupForm.controls.userData.controls.email.pending" class="help-block">
       Please enter valid email
    </span>

Setting and Patching form values

  • Not only can you listen to the updates in your form, you can also update the form on your own just like in the template driven approach, setValue() and patchValue() are there for you.

  • You can on your form as a whole call setValue() and pass a JavaScript object which should now resemble the object structure up here (e.g. userData)

    this.signupForm.setValue({
       userData: {
         username: 'Aakash',
         email: '[email protected]'
       },
       gender: 'male',
       hobbies: ['cooking']
     });
  • So if we do this, we should see that now our form is pre-populated with some values because we immediately call setValue() and of course you can also call this upon the click of a button.

  • And as in the template driven approach, you also have patchValue() if you only want to update a part of the form, like for example change the hobby values

    (this.signupForm.get('hobbies') as FormArray).patchValue(['Game_1', 'Game_2']);
  • Just like template driven approach, in Reactive approach, patchValue() and setValue() are also available and the same is true for reset(), so if we want to reset the form after submitting it, we can simply call reset().

    onSubmit(): void {
      console.log('signupForm: ', this.signupForm);
      this.signupForm.reset();
    }

Example

<div class="container">
    <div class="row">
        <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
            <form [formGroup]="signupForm" (ngSubmit)="onSubmit()" #form="ngForm">
                <div formGroupName="userData">
                    <div class="form-group">
                        <label for="username">Username</label>
                        <input type="text" id="username" formControlName="username" class="form-control">
                        <span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched" class="help-block">
                            <span *ngIf="signupForm.get('userData.username').errors['nameIsForbidden']" class="help-block">Invalid username!</span>
                            <span *ngIf="signupForm.get('userData.username').errors['required']" class="help-block">Please enter valid username</span>
                        </span>
                    </div>
                    <div class="form-group">
                        <label for="email">Email</label>
                        <input type="text" id="email" [formControlName]="'email'" class="form-control">
                        <span class="help-block-pending" *ngIf="signupForm.controls.userData.controls.email.pending">Checking Email Validity...</span>
                        <span *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched && !signupForm.controls.userData.controls.email.pending" class="help-block">Please enter valid email</span>
                    </div>
                </div>
                <!-- <div class="form-group">
                    <label for="username">Username</label>
                    <input type="text" id="username" formControlName="username" class="form-control">
                    <span *ngIf="!signupForm.get('username').valid && signupForm.get('username').touched" class="help-block">Please enter valid username</span>
                </div>
                <div class="form-group">
                    <label for="email">email</label>
                    <input type="text" id="email" [formControlName]="'email'" class="form-control">
                    <span *ngIf="!signupForm.get('email').valid && signupForm.get('email').touched" class="help-block">Please enter valid email</span>
                </div> -->
                <div class="form-group">
                    <label for="gender">Gender</label>
                    <div class="radio" *ngFor="let item of genders">
                        <input type="radio" formControlName="gender" [value]="item">
                        <label>{{ item }}</label>
                    </div>
                </div>
                <div formArrayName="hobbies">
                    <h4>Your Hobbies</h4>
                    <button class="btn btn-default" (click)="onHobby()" type="button">Add Hobby</button>
                    <div class="form-group" *ngFor="let hobbyControl of controls; let i = index">
                        <input type="text" class="form-control" [formControlName]="i">
                    </div>
                    <span *ngIf="!signupForm.get('hobbies').valid && signupForm.get('hobbies').touched" class="help-block">Please enter your hobbies</span>
                </div>
                <button class="btn btn-primary" type="submit" [disabled]="!signupForm.valid">Submit</button>
            </form>
        </div>
    </div>
</div>      
import { Component, OnInit, ViewChild, AfterViewChecked } from '@angular/core';
import { FormGroup, FormControl, NgForm, Validators, FormArray, AbstractControl } from '@angular/forms';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-reactive-forms',
  templateUrl: './reactive-forms.component.html',
  styleUrls: ['./reactive-forms.component.css']
})
export class ReactiveFormsComponent implements AfterViewChecked, OnInit {

  genders: string[] = ['male', 'female', 'others'];
  nationalityArray = ['indian', 'american', 'african'];
  signupForm: FormGroup;
  @ViewChild('form', {static: false}) formData: NgForm;
  forbiddenUserNames: string[] = ['Aakash', 'Goplani'];

  constructor() {  }

  ngAfterViewChecked(): void {
    this.addCheckboxes();
  }

  ngOnInit() {
    this.signupForm = new FormGroup({
      userData: new FormGroup({
        username: new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
        email: new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmails)
      }),
      gender: new FormControl('male'),
      /*
        username: new FormControl(null, Validators.required),
        email: new FormControl(null, [Validators.required, Validators.email]),
      */
      hobbies: new FormArray([])
    });

    this.signupForm.get('userData.username').valueChanges.subscribe(
      (value) => {
        console.log('username value changed: ', value);
      }
    );

    this.signupForm.get('userData.email').statusChanges.subscribe(
      (status) => {
        console.log('email status changed: ', status);
      }
    );

    this.signupForm.setValue({
      userData: {
        username: 'Aakash',
        email: '[email protected]'
      },
      gender: 'male',
      hobbies: ['cooking'],
      nationality: []
    });

    (this.signupForm.get('hobbies') as FormArray).patchValue(['Game_1', 'Game_2']);
  }

  onHobby(): void {
    const checkboxControl = new FormControl(null, Validators.required);
    (this.signupForm.get('hobbies') as FormArray).push(checkboxControl);
  }

  get controls(): AbstractControl[] {
    return (this.signupForm.get('hobbies') as FormArray).controls;
  }

  forbiddenNames(control: FormControl): {[s: string]: boolean} {
    if (this.forbiddenUserNames.indexOf(control.value) !== -1) {
      // tslint:disable-next-line: object-literal-key-quotes
      return {'nameIsForbidden': true};
    }
    return null;
  }

  forbiddenEmails(control: FormControl): Promise<any> | Observable<any> {
    const promise = new Promise<any>((resolve, reject) => {
      setTimeout(() => {
        if (control.value === '[email protected]') {
          // tslint:disable-next-line: object-literal-key-quotes
          resolve({'emailIsForbidden': true});
        } else {
          resolve(null);
        }
      }, 2000);
    });
    return promise;
  }

  onSubmit(): void {
    console.log('signupForm: ', this.signupForm);
    console.log('ngForm: ', this.formData);
    this.signupForm.reset();
  }

}
.container {
    margin-top: 30px;
}

.help-block {
    color: red;
}

input.ng-invalid.ng-touched {
    border: 1px solid red;
}

input.ng-pending {
    border: 2px solid yellow; 
}
.help-block-pending {
    color: blue;
}

input[type='radio'] {
    margin-left: 0;
}

input[type="checkbox"] {
    margin-right: 4px;
}

Example2

<div class="row">
  <div class="card margin-b30">
    <form [formGroup]="formData" (ngSubmit)="saveUpdates()">
      <div class="card-header">Update {{ userToUpdate.displayName + '\'s' }} Details</div>
        <div class="card-body">
          <div class="card-text" formArrayName="user_data">
            <ng-container *ngFor="let userDataControl of getUserDataControl(formData); let i=index" [formGroupName]="i">
              <!-- update user role -->
              <div class="form-group">
                <label for="roleType">Update User Role:</label>
                <select class="form-control" formControlName="access_level">
                  <option *ngFor="let type of roleTypes" [ngValue]="type">{{type}}</option>
                </select>
              </div>
              <!-- update assignd projects -->
              <div class="form-group" formArrayName="user_project_data">
                <label for="roleType">Update Assigned Projects to User:</label>
                <ng-container *ngFor="let projectDataControl of getProjectDataControl(userDataControl); let j=index" [formGroupName]="j">
                  <div class="checkbox">
                    <input type="checkbox" [formControl]="projectDataControl" (change)="getProjectValue()">
                    <label for="{{ userToUpdate.pid[j] }}">{{ userToUpdate.pid[j] }}</label>
                  </div>
                </ng-container>
              </div>
            </ng-container>
          </div>
        </div>
        <div class="card-footer">
          <button (click)="cancelUpdates()" class="btn btn-dark">Cancel</button>
          <button type="submit" [disabled]="!formData.valid" class="btn btn-dark pull-right">Save</button>
        </div>
    </form>
  </div>
</div>
  @Input() userToUpdate: User;

  formData: FormGroup;
  selectedProjects: string[];
  private userProjectDataControl: FormControl[];

  readonly roleTypes: Array<string> = [
    AppConstants.SYSTEM_ADMIN,
    AppConstants.ADVANCE_USER,
    AppConstants.READ_ONLY
  ];

  ngOnInit(): void {
    this.formData = new FormGroup({
      user_data: new FormArray([
        new FormGroup({
          access_level: new FormControl(this.userToUpdate.accessLevel, Validators.required),
          user_project_data: new FormArray(
            this.userToUpdate.pid.map(project => {
              return new FormControl(true); // every project should be selected
            })
          )
        })
      ])
    });
  }

  getUserDataControl(form) {
    return form.controls.user_data.controls;
  }

  getProjectDataControl(form) {
    this.userProjectDataControl = form.controls.user_project_data.controls;
    /**
     * this value is equivalent to:
     * this.formData.controls.user_data['controls'][0]['controls'].user_project_data['controls']
     */
    return form.controls.user_project_data.controls;
  }

  getProjectValue(): void {
    this.selectedProjects = this.userProjectDataControl.map((project, i) => {
      return project.value && this.userToUpdate.pid[i];
    });

    this.selectedProjects = this.selectedProjects.filter((project) => {
      if (!!project) {
        return project;
      }
    });
  }

  saveUpdates(): void {
    ...
    this.getProjectValue();
    ...
  }

  cancelUpdates(): void {
    ...
  }

Related Topics

  1. Angular Forms
  2. Template Driven Forms
⚠️ **GitHub.com Fallback** ⚠️