首页 > 解决方案 > 创建可重用的 Angular 数据输入(邮寄地址)组件

问题描述

我正在创建一个 Angular 11 应用程序,它有 3 个不同的邮寄地址要输入。我以为我新做什么,但我想我没有。我得到了带有非唯一 id 警告的元素。在第一次尝试失败后,我了解了单组件 Angular 模块。我正在我的参考应用程序上尝试一个概念 SCAM 组件。

我收到以下运行时警告:

[DOM] Found 2 elements with non-unique id #Address2:
[DOM] Found 2 elements with non-unique id #Address1:

我的概念地址组件如下:

import { Component, OnInit, Input, Output, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
//
@Component({
  selector: 'app-address',
  template: `
<label for='Address1'>{{addressLabel}}</label>
<input type='text' id='Address1' name='Address1' required maxlength='50' class='nsg-input-wide'
  #Address1='ngModel' [(ngModel)]='address1' (ngModelChange)='onDataChange( $event )'
  placeholder='Address 1...' [disabled]='disabled' />
<div *ngIf='Address1.invalid && Address1.touched' style='color: red;'>
  'Address 1' is required.
</div>
<label for='Address2'>Address 2:</label>
<input type='text' id='Address2' name='Address2' required maxlength='50' class='nsg-input-wide'
  #Address2='ngModel' [(ngModel)]='address2' (ngModelChange)='onDataChange( $event )'
  placeholder='Address 2...' [disabled]='disabled' />
`
})
export class AddressComponent implements OnInit {
  @Input() disabled: boolean = true;
  @Input() addressLabel: string = 'Address:';
  @Input() address1: string = '';
  @Input() address2: string = '';
  //
  constructor() { }
  ngOnInit(): void { }
  onDataChange( event: any ): boolean {
    // this.onFormChanged.emit( event );
    return false;
  }
}
@NgModule({
  declarations: [ AddressComponent ],
  imports: [
    CommonModule,
    FormsModule
  ],
  exports: [ AddressComponent ]
})
export class AddressModule { }

我的 app.component.html 的一部分如下:

...
<app-address
  [disabled]='addressDisabled'
  [addressLabel]='addressLabel_1'
  [address1]='address1_1'
  [address2]='address2_1'>
</app-address>
<br />
<app-address
  [disabled]='addressDisabled'
  [addressLabel]='addressLabel_2'
  [address1]='address1_2'
  [address2]='address2_2'>
</app-address>
...

我的 app.component.ts 的一部分如下:

...
  addressDisabled: boolean = false;
  addressLabel_1: string = 'Office Address:';
  address1_1: string = 'Address 1';
  address2_1: string = 'Address 2';
  addressLabel_2: string = 'Remit Address:';
  address1_2: string = 'Address 1 2';
  address2_2: string = 'Address 2 2';
...

我的 app.module 如下:

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CommonModule } from '@angular/common';
//
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AddressComponent, AddressModule } from './scam/address/address.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    CommonModule,
    AppRoutingModule,
    AddressModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

现在,我想要一个最佳实践解决方案,不重复地址的数据输入。

标签: angulartypescriptinputreusability

解决方案


我建议您研究一下反应式表单,这是创建自定义表单控件和通用表单控件的理想选择,因为您需要传递给孩子的只是表单控件本身,它将保存任何初始值、验证等。在这里您可以应用相同的自定义表单控件不仅适用于地址,还适用于您将拥有的任何其他表单控件

这是一个满足您需求的示例。我会将地址作为 FormGroup 粘贴在 formarray 中。在示例中,我还包含了一个“名称”表单控件,只是为了证明可以为您拥有的任何其他表单控件添加此自定义表单控件。

好的,所以首先添加ReactiveFormsModule您的应用程序模块。然后让我们在您的父级中构建表单:

import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'

// ....

export class AppComponent  {
  myForm: FormGroup;

  // getter to shortened the code needed in template
  get addressArr() {
    return (this.myForm.get('addresses') as FormArray).controls;
  }

  constructor(private fb: FormBuilder) {
    // build form with validators
    this.myForm = this.fb.group({
      name: ['', [Validators.minLength(10)]],
      addresses: this.createAddresses()
    });
  }

    // create form groups and push them to formarray
    createAddresses() {
      let address = this.fb.array([]);
      for(let i = 0; i < 3; i++) {
        address.push(
          this.fb.group({
            address1: [{value: '', disabled: false}, [Validators.maxLength(10)]],
            address2: [{value: '', disabled: false}]
          })
        )
      }
      return address;
    }
}

您正在使用禁用。上面我添加了禁用为false,如果你想最初禁用它,你可以将它定义为true。

子组件看起来像这样,它从父组件接受表单控件,显示字段以及任何可能存在的验证错误:

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

@Component({
  selector: 'hello',
  template: `
    <input [formControl]="ctrl" [placeholder]="placeholder"/>
    <small *ngIf="ctrl.hasError('maxlength')">Max 20 characters!</small>
    <small *ngIf="ctrl.hasError('minlength')">Min 10 characters!</small>
  `
})
export class CustomFormControl  {
  @Input() ctrl: FormControl;
  @Input() placeholder: string;

}

然后在父组件中,我们只需将子组件添加到它们所属的位置(对于formarray,我们需要迭代,命名formgroup,我们使用它的索引)并将formcontrol传递给子组件。就这样!

<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
  <custom-control [ctrl]="myForm.get('name')" placeholder="Name"></custom-control>
  <div *ngFor="let group of addressArr; let i = index">
    <div formArrayName="addresses">
      <div [formGroupName]="i">
        <custom-control [ctrl]="group.get('address1')" placeholder="Address 1"></custom-control>
        <custom-control [ctrl]="group.get('address2')" placeholder="Address 2"></custom-control>
      </div>
    </div>
  </div>
</form>

这是一个Stackblitz供您参考。


推荐阅读