How to implement a material chiplist in Angular 8 using formGroup

Struggling to include a chip list of Angular material within an Ng form? Unable to add a new chip list upon button click and uncertain about displaying the value of the array added in the new chip list. Take a look at this example: https://stackblitz.com/edit/angular-4d5vfj-g1ggqr

   <button (click)="addNewChip()">Add new Chip</button><br><br>

  <form [formGroup]="myForm">
  <mat-form-field class="example-chip-list">
  <mat-chip-list #chipList formArrayName="names">
  <mat-chip 
    *ngFor="let name of myForm.get('names').controls; let i=index;"
    [selectable]="selectable"
    [removable]="removable"
    (removed)="remove(myForm, i)">
    {{name.value}}
    <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
  </mat-chip>
  <input placeholder="Names"
    [matChipInputFor]="chipList"
    [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
    [matChipInputAddOnBlur]="addOnBlur"
    (matChipInputTokenEnd)="add($event, myForm)">
</mat-chip-list>
<mat-error>Atleast 1 name need to be added</mat-error>
</mat-form-field>
 </form>

component.ts file

export class ChipListValidationExample implements OnInit {
@ViewChild('chipList') chipList: MatChipList;
public myForm: FormGroup;

  // name chips
    visible = true;
  selectable = true;
  removable = true;
  addOnBlur = true;
   readonly separatorKeysCodes: number[] = [ENTER, COMMA];

// data
 data = {
  names: ['name1', 'name2']
  }

constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
  names: this.fb.array(this.data.names, this.validateArrayNotEmpty)
});
  }

ngOnInit() {
this.myForm.get('names').statusChanges.subscribe(
  status => this.chipList.errorState = status === 'INVALID'
);
}

 initName(name: string): FormControl {
return this.fb.control(name);
}

 validateArrayNotEmpty(c: FormControl) {
if (c.value && c.value.length === 0) {
  return {
    validateArrayNotEmpty: { valid: false }
  };
}
return null;
 }

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

// Add name
         if ((value || '').trim()) {
  const control = <FormArray>form.get('names');
  control.push(this.initName(value.trim()));
  console.log(control);
}

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

   remove(form, index) {
console.log(form);
form.get('names').removeAt(index);
 }

  addNewChip(){
  console.log("Yse")
   this.myForm = this.fb.group({
  names: this.fb.array(this.data.names, this.validateArrayNotEmpty)
   });
   }

}

Answer №1

Is this the solution you were searching for?

  • I made adjustments to the addChip and removeChip methods so they now operate on the current Chiplist in the FormArray by passing the formControl to the methods.
  • I updated the data structure to accommodate a name and initial set of values.
  • The addNewChipList method was modified to include a formControl to the existing FormArray.
<button (click)="addNewChipList()">Add new Chip</button><br><br>

<form [formGroup]="myForm">
<ng-container formArrayName="names"
  *ngFor="let item of myForm.get('names').controls; let i = index;">
  <mat-form-field class="example-chip-list" [formGroupName]="i">
    <mat-chip-list #chipList >
      <mat-chip *ngFor="let val of item.value.val"
        [selectable]="selectable"
        [removable]="removable"
        (removed)="removeChip(item, val)">
        {{val}}
        <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
      </mat-chip>
      <input [placeholder]="item.value.name"
        [matChipInputFor]="chipList"
        [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
        [matChipInputAddOnBlur]="addOnBlur"
        (matChipInputTokenEnd)="addChip($event, item)">
    </mat-chip-list>
    <mat-error>Atleast 1 name need to be added</mat-error>
  </mat-form-field>
</ng-container>
</form>
// data
data = {
  names: [this.initName('name1'), this.initName('name2', ['A', 'B'])]
}

constructor(private fb: FormBuilder) {
  this.myForm = this.fb.group({
    names: this.fb.array(this.data.names, this.validateArrayNotEmpty)
  });
}

ngOnInit() {
  this.myForm.get('names').statusChanges.subscribe(
    status => this.chipList.errorState = status === 'INVALID'
  );
}

initName(name: string, val: string[]=[]): FormControl {
  return this.fb.control({ name, val });
}

validateArrayNotEmpty(c: FormControl) {
  if (c.value && c.value.length === 0) {
    return {
      validateArrayNotEmpty: { valid: false }
    };
  }
  return null;
}

addChip(event: MatChipInputEvent, ctrl: FormControl): void {
  const input = event.input;
  const value = event.value;

  // Add name
  if ((value || '').trim()) {
    const control = ctrl;
    control.value.val.push(value.trim());
    console.log(control.value);
  }

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

removeChip(ctrl, val) {
  const idx = ctrl.value.val.findIndex(item => item === val);
  ctrl.value.val.splice(idx, 1);
}

addNewChipList() {
  const items = this.myForm.get('names') as FormArray;
  items.push(this.initName(`name${items.length + 1}`));
}

Check out the updated Stackblitz here.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to identify and differentiate objects based on their interface types in JavaScript/TypeScript?

Incorporating a library that defines the following interfaces: LocalUser { data { value: LocalDataValue }, ...various other methods etc... } RemoteUser { data { value: RemoteDataValue }, ...various other methods etc... } A User is then ...

Inferring types from synchronous versus asynchronous parameters

My objective is to create an "execute" method that can deliver either a synchronous or an asynchronous result based on certain conditions: type Callback = (...args: Arguments) => Result const result: Result = execute(callback: Callback, args: Arguments) ...

Ways to utilize a field from an interface as a type of index

interface Mapping { "alpha": (a: string) => void "beta": (b: number) => void } interface In<T extends keyof Mapping> { readonly type: T, method: Mapping[T] } const inHandlers: In<"alpha"> = { type ...

What is the best way to eliminate the left margin entirely in CSS?

I am attempting to create an image view that fully covers the window, without any margins. I have tried various solutions such as setting the body margin and padding to 0, but they do not seem to work. body { margin: 0px; padding: 0px; } or *, html { ...

How can Material UI React handle long strings in menu text wrapping for both mobile and desktop devices?

Is there a way to ensure that long strings in an MUI Select component do not exceed the width and get cut off on mobile devices? How can I add a text-wrap or similar feature? Desktop: Mobile: <FormControl sx={{ m: 1, minWidth: '100%', marg ...

Getting the Final Character from a TypeScript String Constant

Can we extract the final character from a string without using the entire alphabet as an enum? Right now, I'm focusing on numeric digits. I'm somewhat puzzled about why my current approach isn't yielding the correct results. type Digit = &a ...

Exploring the method to implement unit testing for a nested if condition using Karma-Jasmine within an Angular environment

I have a function and my unit test coverage is currently at 75%, but I am aiming for 100% coverage. This is the function in question: calculateRatingSummary(): void { if (this.averageRating > 0) { this.avgRatings = Math.trunc(this.averageRat ...

Duplicate a DOM element and incorporate animation into it

After extensively researching articles and documentation on this topic, I have yet to find a solution that aligns with the approach I am attempting to implement. My scenario involves an array of category items which contain a nested array of products be ...

How to disable the first option in an Angular 2 select dropdown

I'm working with a select component, and here is the code snippet I have: <select name="typeSelection" materialize="material_select" [(ngModel)]="trainingplan.type" > <option [ngValue] = "null" disabled selected>Please choose a ...

"Incorporating multiple router modules in Angular 4/5, with a parent router module overseeing all

Is there a way to have multiple router modules in Angular, such as Core and Main with their own separate routing modules, and then import these two routing modules into our parent routing module (app.routing.ts/app.module.ts)? ...

I'm struggling to understand how to use rxjs 6 in conjunction with angular 6, specifically when it comes to utilizing interval

I'm struggling to update my rxjs code to version 6. Previously, I had the following code that would poll for new data every 5 seconds: import { Observable, interval } from 'rxjs'; import { switchMap, map } from 'rxjs/operators'; ...

NG6002 Error: Detected in the imports section of the AppModule in Angular, but unable to locate a corresponding NgModule class

I recently started using firestore and encountered an error that seems to be related to Ivy after conducting some research. I'm not very experienced in making changes to tsconfig.app.json, but based on other responses, this seems to be the direction I ...

Mapping the response from an http.get call to create a new typed object instance in Angular 2

Can anyone help me understand how to map the result from a service call to an object using http.get and Observables in Angular 2? Please check out this example In my getPersonWithGetProperty method, I am trying to return an Observable of type PersonWithG ...

Utilizing SystemJS to Retrieve Files from Subfolders

Having an issue loading my custom angular2 component, Test.component. When using Systemjs, I've set the default extension to .js for everything under /app. However, for some reason it's not appending ".js" to test.component when searching for it ...

Combining the JSON code coverage reports generated by both Cypress and Karma does not yield an accurate outcome

In my angular project, I am testing it using the built-in unit testing tool (karma) and Cypress. My goal is to combine the code coverage reports from both tests. I have successfully set up the coverage configurations and merged the outputs using `nyc merg ...

Having trouble clicking on a button with Protractor because the button text is located within a child span element

Having trouble clicking a button with protractor. The DOM structure is displayed in the image above. Here are some of the locators I've attempted to use: element(by.xpath("(//div[@class='mat-drawer-backdrop ng-star-inserted'])//a followin ...

Is it possible to devise a universal click handler in TypeScript that will consistently execute after all other click handlers?

In my ReactJS based application written in TypeScript, we have implemented various click handlers. Different teams contribute to the application and can add their own handlers as well. The challenge we face is ensuring that a specific global click handler ...

Issues encountered while attempting to use 'npm install' on Angular, leading to

Having trouble with npm i in my project. It's not working for me, but others can install it smoothly. I've checked all the node and angular versions, but I can't figure out what's missing. Could it be my laptop's compatibility? Ple ...

Utilize a custom Angular2 validator to gain entry to a specific service

For accessing my custom http service from within a static method, consider the following example: import {Control} from 'angular2/common'; import {HttpService} from './http.service'; class UsernameValidator { static usernameExist( ...

Decoding the mysteries of a ZoneAwareError: A guide to unraveling the enigma

Within my Angular 2 application, I utilize a resolve guard that performs an XHR request and throws a custom error if the request fails: return this.service.getProduct (id) .catch (err => Observable.throw(new MyError(err.message, route, 500, err))); ...