带有可重复使用的表单组件的角度材料步进器

问题描述 投票:0回答:2

我正在尝试通过创建一个表单组件来实现 Angular Material Stepper,该组件可以作为多个较大表单的步骤插入。

例如,假设我有N个表单,并且第一步总是相同的(名称和地址信息),我如何将组件插入到父组件中?

我见过其他问题,但是,它们似乎都以某种方式让 Angular 抱怨,即当 strict: true 时。

我尝试了各种方法,有些方法可以通过添加空断言运算符来开始工作,但这让我觉得我没有正确输入内容或以正确的方式进行操作。

这是我尝试过的一个例子。

// insured-form.component.ts

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatSelectModule } from '@angular/material/select';

interface AddressForm {
  line1: FormControl<string>;
  line2: FormControl<string | null>;
  city: FormControl<string>;
  postalCode: FormControl<string>;
  stateId: FormControl<string>;
}

export interface InsuredForm {
  name: FormControl<string>;
  dba: FormControl<string | null>;
  mailingAddress: FormGroup<AddressForm>;
}

@Component({
  selector: 'grid-insured-form',
  standalone: true,
  imports: [
    ReactiveFormsModule,
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule,
    MatIconModule,
    MatSelectModule,
  ],
  template: `<form [formGroup]="insuredForm" class="insured-form">
  <mat-form-field class="insured-form__input">
    <mat-label>Name</mat-label>
    <input type="text" matInput formControlName="name" />
  </mat-form-field>

  <mat-form-field class="insured-form__input">
    <mat-label>DBA</mat-label>
    <input type="text" matInput formControlName="dba" />
  </mat-form-field>

  <div formGroupName="mailingAddress" class="address-form">
    <h2>Mailing Address</h2>

    <mat-form-field class="address-form__input">
      <mat-label>Line 1</mat-label>
      <input type="text" matInput formControlName="line1" />
    </mat-form-field>

    <mat-form-field class="address-form__input">
      <mat-label>Line 2</mat-label>
      <input type="text" matInput formControlName="line2" />
    </mat-form-field>

    <mat-form-field class="address-form__input">
      <mat-label>Postal Code</mat-label>
      <input type="text" matInput formControlName="postalCode" />
    </mat-form-field>

    <mat-form-field class="address-form__input">
      <mat-label>City</mat-label>
      <input type="text" matInput formControlName="city" />
    </mat-form-field>

    <mat-form-field class="address-form__input">
      <mat-label>State</mat-label>
      <mat-select formControlName="stateId">
        <mat-option value="1">FL</mat-option>
      </mat-select>
    </mat-form-field>
  </div>
</form>
`,
  styleUrls: ['./insured-form.component.scss'],
})
export class InsuredFormComponent implements OnInit {
  insuredForm!: FormGroup;

  ngOnInit(): void {
    this.insuredForm = new FormGroup<InsuredForm>({
      name: new FormControl('', { nonNullable: true }),
      dba: new FormControl('', { nonNullable: true }),
      mailingAddress: new FormGroup<AddressForm>({
        line1: new FormControl('', { nonNullable: true }),
        line2: new FormControl(''),
        city: new FormControl('', { nonNullable: true }),
        postalCode: new FormControl('', { nonNullable: true }),
        stateId: new FormControl('', { nonNullable: true }),
      }),
    });
  }
}

// End of insured-form.component.ts
// feature-form.component.ts

import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { MatStepperModule } from '@angular/material/stepper';
import { InsuredFormComponent } from '../shared/insured-form/insured-form.component';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'grid-feature-form',
  standalone: true,
  imports: [MatStepperModule, InsuredFormComponent],
  template: `<mat-stepper orientation="horizontal" #stepper>
  <mat-step [stepControl]="insuredForm">
    <ng-template matStepLabel>Step 1</ng-template>
    <grid-insured-form></grid-insured-form>
  </mat-step>
</mat-stepper>
`,
  styleUrls: ['./feature-form.component.scss'],
})
export class FeatureFormComponent implements AfterViewInit {
  @ViewChild(InsuredFormComponent) insuredFormComponent!: InsuredFormComponent;
  insuredForm!: FormGroup;

  ngAfterViewInit(): void {
    this.insuredForm = this.insuredFormComponent.insuredForm;
  }
}

// End of feature-form.component

以下有效,但我在控制台中出现以下错误:

Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '[object Object]'

替代解决方案:我通过将 insuredForm 创建移动到服务中、将 Input() 添加到 insured-form 组件并在 feature-form 组件中创建表单来使其工作。但是,我真的很想将 insuredForm 封装在 insured-form 组件中。我相信这是可行的,因为 insuredForm(表单组)将在创建时在 NgOnInit 中的功能表单组件中实例化。

我也知道我可以手动运行更改检测,但 still 似乎不是正确的方法。

这不是一个小型的副项目,所以我想确保我以尽可能最好的方式做事。

注意:我正在使用 Angular 16,所以我考虑过使用 Signals,但还没有太多使用它们。

如何创建一个可重用的表单组件,可以插入到 mat-step 中,并避免当 strict 设置为 true 时编译器在控制台中发出任何抱怨?

堆栈闪电战

angular angular-material2 angular-material-stepper
2个回答
0
投票

从错误来看,我认为问题在于运行更改检测后表单值发生了更改。 因此,您必须在“ngAfterViewInit”中显式运行更改检测周期。

屏幕截图:

回复我的答案,让我知道这是否是您正在寻找的内容。 谢谢你。


0
投票

您可以使用

static: true
选项确保查询结果在 ngOnInit 中可用。

它将允许您使用

ngOnInit
而不是
ngAfterViewInit

@ViewChild(InsuredForm, { static: true }) insuredFormComponent!: InsuredForm;
insuredForm!: FormGroup;

ngOnInit(): void {
  this.insuredForm = this.insuredFormComponent.insuredForm;
}

叉式Stackblitz

© www.soinside.com 2019 - 2024. All rights reserved.