1

I am currently working on a project where I have a reactive form array which will be displayed on the template using an *ngIf. I think that's the normal use case and nothing special. But now I build this like

<div *ngFor="let control of form.controls['someArray'].value; let index = index">
  [...]
  <button mat-flat-button color="warn">Delete</button>
</div>
<button mat-flat-button color="primary">Add</button>

The Delete button does not react to my clicks, but the Add button does. For some reason when I remove the let index = index it works again or if I put the button outside of this *ngFor.

I hope you can help me. Thank you very much. Couldn't find any solution so far.

Example: https://stackblitz.com/edit/angular-form-array-example-hq5tud

import { Component } from '@angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `
    <form [formGroup]="form">
      <input type="checkbox" formControlName="published"> Published
      <div *ngIf="form.controls.published.value">

        <h2>Credentials</h2>
        <button (click)="addCreds()">Add</button>

        <div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
          <ng-container [formGroupName]="i">
            <input placeholder="Username" formControlName="username">
            <input placeholder="Password" formControlName="password">
            <button mat-flat-button (click)="buttonClick()"> Test inside </button>
          </ng-container>
        </div>
        <button mat-flat-button (click)="buttonClick()"> Test outside </button>

      </div>
    </form>
  `,
})
export class AppComponent  {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      published: true,
      credentials: this.fb.array([]),
    });
  }

  addCreds() {
    const creds = this.form.controls.credentials as FormArray;
    creds.push(this.fb.group({
      username: '',
      password: '',
    }));
  }

  buttonClick() {
    console.log('Clicked');
  }
}

Ling Vu
  • 4,740
  • 5
  • 24
  • 45
  • Can you add the complete HTML file and the component ts file? – German Quinteros Nov 27 '19 at 21:24
  • I put an example – Ling Vu Nov 27 '19 at 21:29
  • @LingVu Your example is suppose to do what? What do you expect on the test inside? I don't see any code for handling `Test Inside`? – 123 456 789 0 Nov 27 '19 at 21:38
  • This is an aside but you have the same issue I do in my formArray, namely typing in the inputs doesn't work. It seems to lose focus on the keyup. I haven't looked into that particular issue in my project yet. – Steve Nov 27 '19 at 21:46
  • It should behave like the button outside, but instead it does not click or even fire the ripple which a normal mat-button usually do. – Ling Vu Nov 27 '19 at 21:50
  • I don't think you can bind events to an event. I think you might have to rethink your design and create child components where you can control more the events such as the click of the children? – 123 456 789 0 Nov 27 '19 at 21:55
  • The click event of the mat-button just does not fire inside the ngFor. That‘s all. There is no special design. Maybe you don‘t understand the actual problem... – Ling Vu Nov 27 '19 at 22:22

4 Answers4

1

Here is the working example Please check the link

https://stackblitz.com/edit/angular-form-array-example-test123-2jzdij

Ashot Aleqsanyan
  • 4,252
  • 1
  • 15
  • 27
  • Wow it works, can you explain why it behaves now like usual? I saw the main reason for this behavior is the change from `form.controls.credentials?.value` to `form.get('credentials').controls` – Ling Vu Nov 27 '19 at 22:33
  • 1. no but please use `form.get('credentials')` instead of `form.controls.credentials`, when you putted the groups into formArray by the way `form.controls.credentials`. Validators and some events is not working as expected. 2. You have putted the `formGroupName` on the `ng-contaner`. and it is blocked somehow the behavior of your button – Ashot Aleqsanyan Nov 27 '19 at 22:40
1

Finally I found the solution with the help of @ashot-aleqsanyan . The reason for that is still unclear. If anyone has an explanation for this bahavior then please tell us about.

The solution: Replace the form.controls.credentials?.value to form.get('credentials').controls in the .html template does the trick. After that the mat-buttons are working inside the *ngFor again.

import { Component } from '@angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `
    <form [formGroup]="form">
      <input type="checkbox" formControlName="published"> Published
      <div *ngIf="form.controls.published.value">

        <h2>Credentials</h2>
        <button (click)="addCreds()">Add</button>

        <div formArrayName="credentials" *ngFor="let creds of form.get('credentials').controls; let i = index">
          <ng-container [formGroupName]="i">
            <input placeholder="Username" formControlName="username">
            <input placeholder="Password" formControlName="password">
            <button mat-flat-button (click)="buttonClick()"> Test inside </button>
          </ng-container>
        </div>
        <button mat-flat-button (click)="buttonClick()"> Test outside </button>

      </div>
    </form>
  `,
})
export class AppComponent  {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      published: true,
      credentials: this.fb.array([]),
    });
  }

  addCreds() {
    const creds = this.form.controls.credentials as FormArray;
    creds.push(this.fb.group({
      username: '',
      password: '',
    }));
  }

  buttonClick() {
    console.log('Clicked');
  }
}

Ling Vu
  • 4,740
  • 5
  • 24
  • 45
  • Hello Ling Vu I found the new way to make it work, by `trackBy` Please check the link https://stackblitz.com/edit/angular-form-array-example-test123-2jzdij – Ashot Aleqsanyan Dec 12 '19 at 15:13
  • I noticed that when you clicked on the button it rewrite all of the items ngFor, when I have added the trackBy on your example it works, But I can't undarstand why it is working with `form.get('credentials').controls` – Ashot Aleqsanyan Dec 12 '19 at 15:17
0

I don't know why, but like you say the (click) event is not being trigger inside the <ng-container [formGroupName]="i"> when you use the directives mat-flat-button inside the button <button mat-flat-button> Test outside </button>

If you remove the mat-flat-button it works: <button (click)="myFunc()"> Test outside </button>

Here is the code that I test with https://stackblitz.com/edit/angular-form-array-example-hq5tud :

app.component.ts :

import { Component } from '@angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `
    <form [formGroup]="form">
      <input type="checkbox" formControlName="published"> Published
      <div *ngIf="form.controls.published.value">

        <h2>Credentials</h2>
        <button (click)="addCreds()">Add</button>

        <div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
          <ng-container [formGroupName]="i">
            <input placeholder="Username" formControlName="username">
            <input placeholder="Password" formControlName="password">
            <button class="mat-flat-button" (click)="testFunc()"> Test inside </button>
          </ng-container>
        </div>
        <button mat-flat-button (click)="testFunc()"> Test outside </button>

      </div>
    </form>
  `,
})
export class AppComponent  {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      published: true,
      credentials: this.fb.array([]),
    });
  }

  addCreds() {
    const creds = this.form.controls.credentials as FormArray;
    creds.push(this.fb.group({
      username: '',
      password: '',
    }));
  }

  testFunc() {
    console.log('hello');
  }
}
German Quinteros
  • 1,870
  • 9
  • 33
0

I finally found out the reason behind this, mat-button rely on FocusMoniter, which add an additional capture phase event listener, without a proper trackBy provided, capture phase event will trigger doDocheck of ngFor and remove the old element, adding a new one.So the click event will never reach the original element since the original element already be removed.

put a breakpoint on that ngFor element to see the node be removed

I write an article explaining this problem in detail, although in my cause the event is triggered by the event listern added by overlay, the cause is exactly the same.