You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 15中通过@Input()传递FormControl的取值异常问题

Angular Material Chips自定义组件FormControl值同步问题

问题现象

使用自定义Angular Material Chips组件时,从任务创建组件传递FormControl出现以下异常:

  • 首次点击提交按钮,FormGroup中skills控件的值无法获取
  • 再次点击提交能显示Chips数组值,但修改标签后又显示null

相关组件代码

Chips自定义组件

HTML模板

<mat-form-field [formGroup]="formGroup" class="example-chip-list" appearance="fill">
  <label [for]="controlName" class="control-label">{{label}}</label>
  <mat-chip-grid [formGroup]="formGroup" [formControlName]="controlName" #chipGrid aria-label="Tag selection">
    <mat-chip-row *ngFor="let tag of selectedTags" (removed)="remove(tag)">
      {{tag}}
      <button matChipRemove [attr.aria-label]="'remove ' + tag">
        <mat-icon>cancel</mat-icon>
      </button>
    </mat-chip-row>
  </mat-chip-grid>

  <div class="input-group">
    <img [src]="icon" class="form-control-feedback">
    <input [placeholder]="placeholder" #tagsInputs
           [formGroup]="formGroup"
           [formControlName]="controlName"
           [matChipInputFor]="chipGrid" [matAutocomplete]="auto"
           [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
           (matChipInputTokenEnd)="add($event)" [required]="isRequired">
  </div>
  <span *ngIf="errorMessage != null">
    {{errorMessage}}
  </span>
  <mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
    <mat-option *ngFor="let tags of filteredTagsChange | async" [value]="tags">
      {{tags}}
    </mat-option>
  </mat-autocomplete>
</mat-form-field>

TypeScript代码

import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {
  Component,
  ElementRef,
  ViewChild,
  OnInit,
  Input,
  AfterViewInit
} from '@angular/core';
import {MatAutocompleteSelectedEvent} from '@angular/material/autocomplete';
import {MatChipInputEvent} from '@angular/material/chips';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
import {FormControl, FormGroup} from "@angular/forms";

@Component({
  selector: 'app-custom-chips-feild',
  templateUrl: './custom-chips-filed.component.html',
  styleUrls: ['./custom-chips-filed.component.scss']
})
export class CustomChipsFiledComponent implements OnInit,AfterViewInit {
  separatorKeysCodes: number[] = [ENTER, COMMA];
  @Input() class!: string;
  @Input() label!: string;
  @Input() type!: string;
  @Input() placeholder!: string;
  @Input() icon!: string;
  @Input() controlName!: string;
  @Input() formGroup!: FormGroup;
  @Input() isRequired!: boolean;
  @Input() errorMessage!: string;
  @Input() selectedTags!:string[];
  @Input() allTags!:string[];
  filteredTagsChange!: Observable<string[]>;
  @ViewChild('tagsInputs') tagsInputs!: ElementRef<HTMLInputElement>;

  get chipsController():FormControl{
    if (this.formGroup) {
      return this.formGroup.get(this.controlName) as FormControl;
    } else {
      return new FormControl();
    }
  }

  constructor() {} 

  ngOnInit(): void {
    // 初始化过滤逻辑,确保formGroup已传入
    this.filteredTagsChange = this.chipsController.valueChanges.pipe(
      startWith(''),
      map((tags: string | null) => (tags ? this._filter(tags) : this.allTags.slice())),
    );
    // 初始化时同步FormControl值到selectedTags
    if(this.chipsController.value && Array.isArray(this.chipsController.value)){
      this.selectedTags = [...this.chipsController.value];
    }
  }

  add(event: MatChipInputEvent): void {
    const value = (event.value || '').trim();

    if (this.allTags.includes(value) && value && !this.selectedTags.includes(value)) {
      this.selectedTags.push(value);
      this.allTags = this.allTags.filter(fruit => fruit !== value);
      // 同步更新FormControl值
      this.chipsController.setValue([...this.selectedTags]);
    }

    event.chipInput!.clear();
    this.chipsController.setValue([...this.selectedTags]);
  }

  selected(event: MatAutocompleteSelectedEvent): void {
    const value = event.option.value;

    if (value && !this.selectedTags.includes(value)) {
      this.selectedTags.push(value);
      this.allTags = this.allTags.filter(fruit => fruit !== value);
      // 同步更新FormControl值
      this.chipsController.setValue([...this.selectedTags]);
    }

    this.tagsInputs.nativeElement.value = '';
    this.chipsController.setValue([...this.selectedTags]);
  }

  remove(tag: string): void {
    const index = this.selectedTags.indexOf(tag);

    if (index >= 0) {
      this.selectedTags.splice(index, 1);
      // 将标签放回可选列表
      this.allTags.push(tag);
      // 同步更新FormControl值
      this.chipsController.setValue([...this.selectedTags]);
    }
  }

  private _filter(value: string): string[] {
    const filterValue = value.toLowerCase();
    return this.allTags.filter(fruit => fruit.toLowerCase().includes(filterValue));
  } 
}

任务创建组件

HTML模板

<div class="task_creation_form">
  <form [formGroup]="formGroup" (ngSubmit)="submit()">
    <app-custom-textarea placeholder="Enter your Description" [controlName]="'description'" [formGroup]="formGroup" label="Task Description" [rows]="3" icon="assets/Icons/details.svg"></app-custom-textarea>
    <div class="task_assigning_group">
      <app-custom-dropdown [dropdownOptions]="rolesItem" label="Role" placeholder="Enter your Role" icon="assets/Icons/role.svg" subIcon="assets/Icons/dropdown.svg"
                           [controlName]="'type'" [formGroup]="formGroup" required="true"/>
      <app-custom-dropdown [dropdownOptions]="rolesItem" label="Role" placeholder="Enter your Role" icon="assets/Icons/role.svg" subIcon="assets/Icons/dropdown.svg"
                           [controlName]="'priority'" [formGroup]="formGroup" required="true"/>
    </div>
    <app-custom-chips-feild
      label="Skills"
      placeholder="Enter your Skills"
      icon="assets/Icons/skills.svg"
      [formGroup]="formGroup"
      [controlName]="'skills'"
      [allTags]="allTags"
      [selectedTags]="selectedTags"
      required="true"></app-custom-chips-feild>
    <app-custom-submit-button [isSubmit]="true" [btnName]="'Register'" class="auth-form-submit" [formGroup]="formGroup" />
  </form>
</div>

TypeScript代码

import {Component} from '@angular/core';
import {AuthService} from "../../../../services/auth.service";
import {RegistrationEmailService} from "../../../../services/registration-email.service";
import {FormControl, FormGroup, Validators} from "@angular/forms";

@Component({
  selector: 'app-task-creation',
  templateUrl: './task-creation.component.html',
  styleUrls: ['./task-creation.component.scss']
})
export class TaskCreationComponent {
  selectedTags: string[] = [];
  allTags: string[] = ['Apple', 'Lemon', 'Lime', 'Orange', 'Strawberry'];

  rolesItem = [
    {id: 1, name: 'Admin'},
    {id: 2, name: 'User'},
  ];
  formGroup: FormGroup = new FormGroup({
    description: new FormControl('', [
      Validators.required,
    ]),
    type : new FormControl('', [
      Validators.required,
    ]),
    priority : new FormControl('', [
      Validators.required,
    ]),
    // 初始值设为空数组,匹配标签数组类型
    skills: new FormControl([],[]),
  })

  constructor(private authService:AuthService,private emailService:RegistrationEmailService){}

  submit() {
    console.log(this.formGroup.value);
  }
}

问题修复说明

  1. 硬编码控件名修复:将Chips组件中chipsController getter里的硬编码'skills'改为传入的controlName,确保组件复用性和正确性。
  2. 生命周期调整:将filteredTagsChange的初始化逻辑从构造函数移到ngOnInit,此时@Input的formGroup已完成传递,避免订阅空FormControl的valueChanges。
  3. 值同步机制:在增删标签的add、selected、remove方法中,每次修改selectedTags后,同步更新chipsController的值,确保FormGroup中的值与UI保持一致。
  4. 初始值类型匹配:Task组件中skills FormControl的初始值从空字符串改为空数组,与标签数组类型一致,避免类型不匹配导致的异常。

内容的提问来源于stack exchange,提问作者JAFFAR ABBAS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 21:32:01