Angular: ControValueAccessor with FromArray

176 阅读1分钟
  1. 父组件 my-form.component.html
<div class="my-form" [formGroup]="form">
    <div>
        <label class="form-label">comsumer:</label>
        <input class="form-control" type="text" formControlName="comsumer">
    </div>
    <div>
        <h3>Books</h3>
        <app-books formControlName="books"></app-books>
    </div>
</div>

<div>
    {{form.getRawValue() | json}}
</div>

my-form.component.ts

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

@Component({
  selector: 'app-my-form',
  templateUrl: './my-form.component.html',
  styleUrls: ['./my-form.component.css']
})
export class MyFormComponent implements OnInit {
  form!: FormGroup;
  mockData = {
    comsumer: 'Zoey',
    books: [
      {
        bookName: 'Little Prices',
        bookPrice: '12.0',
        bookAuthor: 'Tom'
      },
      {
        bookName: 'Snow White',
        bookPrice: '10.0',
        bookAuthor: 'Jenney'
      }
    ]
  }

  ngOnInit(): void {
    this.form = new FormGroup({
      comsumer: new FormControl(),
      books: new FormControl()
    });
    this.form.patchValue(this.mockData);
    this.form.get('books')?.valueChanges.subscribe(value => {
      console.log('books changed:', value)
    })
  }
}

  1. 子组件实现ControlValueAccessor并且本身维护一个FormArray books.component.html
<div class="mb-3 d-flex justify-content-end">
    <button type="button" class="btn btn-primary" (click)="addNew()">Add Book</button>
</div>

<div class="d-flex" *ngIf="booksArray">
    <ng-container *ngFor="let group of bookFormArrayControls; let i = index;">
         <div class="card me-2" style="width: 18rem;" [formGroup]="group">
            <ul class="list-group list-group-flush">
              <li class="list-group-item">
                <label class="form-label">Book Name:</label>
                <input class="form-control" formControlName="bookName" />
              </li>
              <li class="list-group-item">
                <label class="form-label">Book Price:</label>
                <input class="form-control" formControlName="bookPrice" />
              </li>
              <li class="list-group-item">
                <label class="form-label">Book Author:</label>
                <input class="form-control" formControlName="bookAuthor" />
              </li>
            </ul>
            <div class="card-body">
              <button class="btn btn-primary" (click)="deleteBook(i)">Delete</button>
            </div>
          </div>
    </ng-container>
</div>

books.component.ts

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

@Component({
  selector: 'app-books',
  templateUrl: './books.component.html',
  styleUrls: ['./books.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: BooksComponent,
      multi: true
    }
  ]
})
export class BooksComponent implements ControlValueAccessor {
  booksArray!: FormArray;
  disabled: boolean = false;
  onChange!: (val: any) => void;
  onTouch!: () => void;

  get bookFormArrayControls() {
    return this.booksArray.controls as FormGroup[];
  }

  writeValue(value: any[]): void {
    if (!value?.length) return;
     
    const arr = value.map(x => {
      return new FormGroup({
        bookName: new FormControl(x.bookName),
        bookPrice: new FormControl(x.bookPrice),
        bookAuthor: new FormControl(x.bookAuthor)
      })
    });

    this.booksArray = new FormArray(arr);
    
    this.booksArray?.valueChanges.subscribe((books: any) => this.onChange(books))
    
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  addNew() {
    const newBook = new FormGroup({
      bookName: new FormControl(''),
      bookPrice: new FormControl(''),
      bookAuthor: new FormControl('')
    })
    this.booksArray.push(newBook)
  }

  deleteBook(index: number) {
    this.booksArray.removeAt(index)
  }

}
  1. 页面

image.png