首页 > 解决方案 > 从发送到父模型Angular 9的子组件获取值

问题描述

我创建了一个简单的表单,作为该表单的一部分,我想使用材料表选择而不是选择字段来实现用户选择,以便在选择用户之前查看用户的详细信息。

我得到了表单和 selectmodel 工作但是我不知道如何将 selectionmodel 数据返回到父模型并将其绑定到表单控件。所以希望得到一些帮助

示例代码:

分配.组件.HTML

<form [formGroup]="newAllocationForm">
  <mat-horizontal-stepper formArrayName="formArray" linear #stepper>
    <mat-step formGroupName="0" [stepControl]="formArray.get([0])">
      <ng-template matStepLabel>New Capability Details</ng-template>
      <div class="row">
        <div class="col">
          <mat-form-field class="full-width">
            <input matInput placeholder="Vehicle ID" formControlName="vehicleId" required>
            <mat-error>
              Vehicle Id is <strong>required</strong>
            </mat-error>
          </mat-form-field>
        </div>
      </div>
      <div>
        <button mat-button matStepperNext type="button">Next</button>
      </div>
    </mat-step>
    <mat-step formGroupName="1" [stepControl]="formArray.get([1])">
      <ng-template matStepLabel>Select Users</ng-template>
      <app-user-table></app-user-table>
      <div>
        <button mat-button matStepperPrevious type="button">Back</button>
        <button mat-button matStepperNext type="button">Next</button>
      </div>
    </mat-step>
    <mat-step>
      <ng-template matStepLabel>Done</ng-template>
      <p>You are now done.</p>
      <div>
        <button mat-raised-button color="primary" type="submit" (click)="onSubmit()"
          [disabled]="newAllocationForm.invalid">Submit</button>

        <button mat-button matStepperPrevious>Back</button>
        <button mat-button (click)="stepper.reset()">Reset</button>
      </div>
    </mat-step>
  </mat-horizontal-stepper>
</form>

分配组件.ts

export class CapabilityNewComponent implements OnInit {
  newAllocationForm= this.fb.group({
    formArray: this.fb.array([
      this.fb.group({
        vehicleId: ['', Validators.required],
      }),
      this.fb.group({
        users: ['']
      })
    ])
  });

  constructor(private fb: FormBuilder) { }

  ngOnInit(): void {
  }

  get formArray(): FormArray | null { return this.newAllocationForm.get('formArray') as FormArray; }

  onSubmit() {
    // get values from child component table and attach to parent form
  }

}

用户表.component.ts

export class UserTableComponent implements OnInit {
  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatTable) table: MatTable<UserTableItem>;
  dataSource: MatTableDataSource<any>;

  selection = new SelectionModel<any>(true, []);

  /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
  displayedColumns = ['select', 'firstName', 'lastName', 'position'];

  constructor(public userService: UserService) {
    userService.getUsers().subscribe(res => console.log(res));
  }

  ngOnInit() {
    this.userService.getUsers().subscribe(res => {
      this.dataSource = new MatTableDataSource(res);
      this.dataSource.sort = this.sort;
      this.dataSource.paginator = this.paginator;
    });
  }

  applyFilter(event: Event) {
    const filterValue = (event.target as HTMLInputElement).value;
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }

  isAllSelected() {
    const numSelected = this.selection.selected.length;
    const numRows = this.dataSource.data.length;
    return numSelected === numRows;
  }

  masterToggle() {
    this.isAllSelected() ?
      this.selection.clear() :
      this.dataSource.data.forEach(row => this.selection.select(row));
  }
}

user-table.component.html

<div class="mat-elevation-z8">
  <table mat-table [dataSource]="dataSource" class="full-width-table" matSort aria-label="Elements">
    <ng-container matColumnDef="select">
      <th mat-header-cell *matHeaderCellDef>
        <mat-checkbox (change)="$event ? masterToggle() : null" [checked]="selection.hasValue() && isAllSelected()"
          [indeterminate]="selection.hasValue() && !isAllSelected()">
        </mat-checkbox>
      </th>
      <td mat-cell *matCellDef="let row">
        <mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
          [checked]="selection.isSelected(row)">
        </mat-checkbox>
      </td>
    </ng-container>

    <ng-container matColumnDef="firstName">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>First Name</th>
      <td mat-cell *matCellDef="let row">{{row.FirstName}}</td>
    </ng-container>

    <ng-container matColumnDef="lastName">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Last Name</th>
      <td mat-cell *matCellDef="let row">{{row.LastName}}</td>
    </ng-container>

    <ng-container matColumnDef="position">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Position</th>
      <td mat-cell *matCellDef="let row">{{row.Position}}</td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>

  <mat-paginator #paginator [length]="dataSource?.data.length" [pageIndex]="0" [pageSize]="10"
    [pageSizeOptions]="[25, 50, 100, 250]">
  </mat-paginator>
</div>

如您所见,这主要是取自 Angular Material 网站的标准代码(没什么花哨的)。我研究了@Output,但无法理解它在这个模型上的工作方式。

任何意见,将不胜感激。

标签: angularangular-material

解决方案


要将实例UserTableComponent用作常规表单控件,您需要它来实现ControlValueAccessor接口。

我在这里整理了一个 Stackblitz 演示。要查看输出,请不要使用 Stackblitz 嵌入式开发工具。使用浏览器的原生开发工具

此接口允许任何自定义角度组件成为角度形式的一部分。

它由4种方法组成:

  1. writeValue(obj: any): void 这个方法被 angular 用来在你使用AbstractControl#setValue.

  2. registerOnChange(fn: any): void 当fn组件的整体值发生变化时,必须调用通过 angular 传递给组件的方法 ( )。新值必须作为参数传递。

  3. registerOnTouched(fn: any): voidfn当你认为你的组件必须通知 angular 它被触摸时,必须调用由 angular 传递给你的组件的方法。

  4. setDisabledState(isDisabled: boolean)?: void 这个方法是可选的,每当父组件调用AbstractControl#enableAbstractControl#disable

    做完以上4项后,还需要提供你的类作为控件值访问器提供者。

    所以:

@Component({
...
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => UserTableComponent),
    multi: true
  }]
})
export class UserTableComponent implements ControlValueAccessor, OnInit {
  onChange: (any[] | null) => void = 
      (SelectionModel<any> | null) => {};

  onTouched: () =>  void = () => {};

  writeValue(value: SelectionModel<any> | null) {
    // set the values of your component
  }

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

  registerOnTouched(fn: () => void) {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean) {
    if(isDisabled) {
      // prevent your table to be selected
    } else {
      // enable your table for being selected
    }
  }

  // whenever the selection changes, you should emit it again
  notifyChanges() {
    this.onChange(this.selection);
  }
}

在您的表格组件中,您将拥有以下内容:

<mat-checkbox (click)="$event.stopPropagation()" 
              (change)="_setSelection($event, row)"
              [checked]="selection.isSelected(row)">
</mat-checkbox>

在你的打字稿中:

_setSelection(row: any, change: EventEmitter<MatCheckboxChange>) {
  if(change) {
    selection.toggle(row);
    this.onChange(this.selection.selected);
  }
}

之后你可以这样做:

<app-user-table formControlName="controlName"></app-user-name>

推荐阅读