Sun
Sun

Reputation: 4718

How to setup Angular Material Chip Contol with reactive forms

I have an Angular Reactive Form with Angular Materials

For all my controls I add the required validator.

I'm not sure how to setup the chips control correctly with reactive forms.

Where do you set the formControlName so that the required validator fires? At the moment I have it set on the input field which I'm guessing is wrong.

I just want the courseIds to be a comma seperated string with the course ids.

TS:

form: FormGroup;

ngOnInit() {
    this.form = new FormGroup({
      name: new FormControl("", [Validators.required]),
      courseIds: new FormControl("", Validators.required)
    });
  }

HTML:

  <form [formGroup]="form" (ngSubmit)="submit()">

    <mat-form-field>
      <input matInput type="text" formControlName="name" placeholder="Name">
    </mat-form-field>

    <mat-form-field>
      <mat-chip-list #chipList>
        <mat-chip *ngFor="let cid of courseIds" (removed) = "...">
          {{cid}}
        </mat-chip>

        <input matInput formControlName="courseIds"
               [matChipInputFor]="chipList" 
               placeholder="Ids" 
               (matChipInputTokenEnd)="add($event)">
      </mat-chip-list>
   </mat-form-field>    

....

    <button type="submit">OK</button>
  </form>

Upvotes: 27

Views: 34142

Answers (2)

Michael Vonck
Michael Vonck

Reputation: 376

When combining FormArrays with nash11 his answer it won't work. I had the problem that when i edit another formArray in the same form group the changes in the mat-chip list where lost.

Therefore here is my solution how to use material chip control with reactive forms + forms array.

https://stackblitz.com/edit/angular-9gjwo4-h4f9ux?file=app/chips-input-example.ts

chips-input-example.html:

<form [formGroup]="form">

    <mat-form-field class="example-chip-list">
        <mat-chip-list #chipList aria-label="Fruit selection" formArrayName="fruits">
            <mat-chip *ngFor="let fruit of fruitControls.value" [selectable]="selectable" [removable]="removable"
                (removed)="remove(fruit)">
                {{fruit}}
                <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
            </mat-chip>
            <input placeholder="New fruit..."
           [matChipInputFor]="chipList"
           [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
           [matChipInputAddOnBlur]="addOnBlur"
           (matChipInputTokenEnd)="add($event)">
  </mat-chip-list>
    </mat-form-field>
</form>

chips-input-example.ts

import { COMMA, ENTER } from "@angular/cdk/keycodes";
import { Component } from "@angular/core";
import { MatChipInputEvent } from "@angular/material/chips";
import {
  FormControl,
  FormGroup,
  FormGroupDirective,
  NgForm,
  Validators,
  FormBuilder,
  FormArray
} from "@angular/forms";

/**
 * @title Chips with input
 */
@Component({
  selector: "chips-input-example",
  templateUrl: "chips-input-example.html",
  styleUrls: ["chips-input-example.css"]
})
export class ChipsInputExample {
  visible = true;
  selectable = true;
  removable = true;
  addOnBlur = true;
  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      fruits: this.fb.array(["Lemon", "Lime", "Apple"], Validators.required)
    });
  }

  get fruitControls(): FormArray {
    return this.form.controls.fruits as FormArray;
  }

  add(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;

    // Add our fruit
    if ((value || "").trim()) {
      this.fruitControls.push(this.fb.control(value));
    }

    // Reset the input value
    if (input) {
      input.value = "";
    }
  }

  remove(fruit: string): void {
    const index = this.fruitControls.value.indexOf(fruit);
    if (index >= 0) {
      this.fruitControls.removeAt(index);
    }
  }
}

Upvotes: 9

nash11
nash11

Reputation: 8680

Try setting the formControlName at the <mat-chip-list> level.

In your template, set the ngFor to loop over the courseIds control value

<mat-form-field>
    <mat-chip-list #chipList formControlName="courseIds">
        <mat-chip *ngFor="let cid of form.get('courseIds').value" (removed) = "...">
            {{cid}}
        </mat-chip>

        <input matInput
            [matChipInputFor]="chipList" 
            placeholder="Ids" 
            (matChipInputTokenEnd)="add($event)">
    </mat-chip-list>
</mat-form-field>

Then in your component, create the form group with the initial values for courseIds if any, else use an empty array [] (since the chips display an array and not a string). In your add() and remove() function, add and remove the values from the courseIds control value respectively.

form: FormGroup;

ngOnInit() {
    this.form = new FormGroup({
        name: new FormControl("", [Validators.required]),
        courseIds: new FormControl([], Validators.required)
    });
}

add() {
    ...

    // Add new input to courseIds control value
    this.courseIds.value.push(value);

    this.courseIds.updateValueAndValidity();
}

remove() {
    ...

    // Remove element from control value array
    this.courseIds.value.splice(index, 1);    // where index = index of removed element

    this.courseIds.updateValueAndValidity();
}

// use getter method to access courseIds control value easily
get courseIds() { 
    return this.form.get('courseIds');
}

Upvotes: 43

Related Questions