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); } }
问题修复说明
- 硬编码控件名修复:将Chips组件中
chipsControllergetter里的硬编码'skills'改为传入的controlName,确保组件复用性和正确性。 - 生命周期调整:将
filteredTagsChange的初始化逻辑从构造函数移到ngOnInit,此时@Input的formGroup已完成传递,避免订阅空FormControl的valueChanges。 - 值同步机制:在增删标签的
add、selected、remove方法中,每次修改selectedTags后,同步更新chipsController的值,确保FormGroup中的值与UI保持一致。 - 初始值类型匹配:Task组件中
skillsFormControl的初始值从空字符串改为空数组,与标签数组类型一致,避免类型不匹配导致的异常。
内容的提问来源于stack exchange,提问作者JAFFAR ABBAS
相关产品推荐
相关产品推荐

