Ionic7中多实例自定义子组件数据与事件绑定异常问题
问题描述
在Ionic7项目中,父页面包含三个<app-select>自定义组件实例,分别传入districts、parishes、villages不同数组数据。组件初始可正常展示对应数据,但点击任意实例时,弹出的模态框始终显示第一个实例的districts数据,选中值也始终绑定到第一个实例。尝试通过open方法传参后数据显示正常,但选中事件仍触发第一个实例的回调函数。
父组件代码
home.page.html
<ion-content> <app-select title="Select District" [data]="districts" itemTextField="text" [multiple]=false (click)="select.open()" (selectedChanged)="districtChanged($event)" #select></app-select> <app-select title="Select Parish" [data]="parishes" itemTextField="text" [multiple]=false (click)="select.open()" (selectedChanged)="parishChanged($event)" #select></app-select> <app-select title="Select Village" [data]="villages" itemTextField="text" [multiple]=false (click)="select.open()" (selectedChanged)="villageChanged($event)" #select></app-select> </ion-content>
home.page.ts
import { Component, OnInit, ViewChild } from '@angular/core'; import { IonDatetime, IonModal } from '@ionic/angular'; export interface Item { text: string; value: string; } @Component({ selector: 'app-add-exporter', templateUrl: './add-exporter.page.html', styleUrls: ['./add-exporter.page.scss'], }) export class AddExporterPage implements OnInit { @ViewChild('modal', { static: true }) modal!: IonModal; public selectedDistrict: String = ''; public selectedParish: String = ''; public selectedVillage: String = ''; public district: any; public districts: Item[] = [ { text: 'District 1', value: 'District 1' }, { text: 'District 2', value: 'District 2' }, { text: 'District 3', value: 'District 3' }, { text: 'District 4', value: 'District 4' }, { text: 'District 5', value: 'District 5' }, { text: 'District 6', value: 'District 6' }, { text: 'District 7', value: 'District 7' }, { text: 'District 8', value: 'District 8' }, { text: 'District 9', value: 'District 9' } ]; public parish: any; public parishes: Item[] = [ { text: 'Parish 1', value: 'Parish 1' }, { text: 'Parish 2', value: 'Parish 2' }, { text: 'Parish 3', value: 'Parish 3' }, { text: 'Parish 4', value: 'Parish 4' }, { text: 'Parish 5', value: 'Parish 5' }, { text: 'Parish 6', value: 'Parish 6' }, { text: 'Parish 7', value: 'Parish 7' }, { text: 'Parish 8', value: 'Parish 8' }, { text: 'Parish 9', value: 'Parish 9' } ]; public village: any; public villages: Item[] = [ { text: 'Village 1', value: 'Village 1' }, { text: 'Village 2', value: 'Village 2' }, { text: 'Village 3', value: 'Village 3' }, { text: 'Village 4', value: 'Village 4' }, { text: 'Village 5', value: 'Village 5' }, { text: 'Village 6', value: 'Village 6' }, { text: 'Village 7', value: 'Village 7' }, { text: 'Village 8', value: 'Village 8' }, { text: 'Village 9', value: 'Village 9' } ]; constructor() { } ngOnInit() { } districtChanged(event: any){ this.selectedDistrict = event[0].text; } parishChanged(event: any){ this.selectedParish = event[0].text; } villageChanged(event: any){ this.selectedVillage = event[0].text; } }
子组件代码
select.component.html
<div *ngIf="selected.length; else placeholder"> <span *ngFor="let item of selected; let last = last"> {{ leaf(item) }}{{ last ? '' : ', '}} </span> </div> <ng-template #placeholder>Select</ng-template> <ion-modal [isOpen]="isOpen" (willDismiss)="cancel()"> <ng-template> <ion-header class="ion-no-border"> <ion-toolbar color="primary"> <ion-title>{{ title }}</ion-title> </ion-toolbar> </ion-header> <ion-content class="ion-padding-horizontal"> <ion-item lines="full" *ngFor="let item of filtered"> <ion-checkbox slot="start" labelPlacement="end" justify="start" [(ngModel)]="item.selected" (ionChange)="itemSelected()" > {{ leaf(item) }} </ion-checkbox> </ion-item> </ion-content> </ng-template> </ion-modal>
select.component.ts
import { CommonModule } from '@angular/common'; import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges} from '@angular/core'; import { IonicModule, SearchbarCustomEvent } from '@ionic/angular'; @Component({ standalone: true, imports: [IonicModule, CommonModule, FormsModule], selector: 'app-select', templateUrl: './select.component.html', styleUrls: ['./select.component.scss'], }) export class SelectComponent implements OnChanges { @Input() title = 'Search'; @Input() data: any = []; @Input() multiple: boolean = false; @Input() itemTextField = 'name'; @Output() selectedChanged: EventEmitter<any> = new EventEmitter(); isOpen: boolean = false; selected: any[] = []; filtered: any[] = []; constructor() { } ngOnChanges(changes: SimpleChanges): void { this.filtered = this.data; } open(){ this.isOpen = true; this.filtered = this.data; } cancel(){ this.isOpen = false; } select(){ this.isOpen = false; } leaf(obj: any){ return this.itemTextField.split('.').reduce((val, el) => val[el], obj); } itemSelected(){ if(!this.multiple){ if(this.selected.length){ this.selected[0].selected = false; } this.selected = this.data.filter((item: any) => item.selected); this.selectedChanged.emit(this.selected); this.isOpen = false; } } }
修复方案
1. 父组件模板:使用唯一模板引用变量
三个<app-select>使用了重复的#select引用变量,Angular只会识别第一个匹配的组件,导致所有点击都触发第一个组件的open方法。修改为唯一变量:
<ion-content> <app-select title="Select District" [data]="districts" itemTextField="text" [multiple]=false (click)="districtSelect.open()" (selectedChanged)="districtChanged($event)" #districtSelect></app-select> <app-select title="Select Parish" [data]="parishes" itemTextField="text" [multiple]=false (click)="parishSelect.open()" (selectedChanged)="parishChanged($event)" #parishSelect></app-select> <app-select title="Select Village" [data]="villages" itemTextField="text" [multiple]=false (click)="villageSelect.open()" (selectedChanged)="villageChanged($event)" #villageSelect></app-select> </ion-content>
2. 子组件:避免修改原数据,创建副本处理状态
子组件直接修改传入的data数组元素的selected属性,会导致原数组被污染,多个组件共享引用时状态混乱。修改为深拷贝数据:
import { CommonModule } from '@angular/common'; import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges} from '@angular/core'; import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; @Component({ standalone: true, imports: [IonicModule, CommonModule, FormsModule], selector: 'app-select', templateUrl: './select.component.html', styleUrls: ['./select.component.scss'], }) export class SelectComponent implements OnChanges { @Input() title = 'Search'; @Input() data: any = []; @Input() multiple: boolean = false; @Input() itemTextField = 'name'; @Output() selectedChanged: EventEmitter<any> = new EventEmitter(); isOpen: boolean = false; selected: any[] = []; filtered: any[] = []; constructor() { } ngOnChanges(changes: SimpleChanges): void { // 深拷贝数据,避免修改原数组 this.filtered = JSON.parse(JSON.stringify(this.data)); } open(){ this.isOpen = true; // 每次打开重置数据,同步已选中状态 this.filtered = JSON.parse(JSON.stringify(this.data)); this.filtered.forEach(item => { item.selected = this.selected.some(selectedItem => selectedItem.value === item.value); }); } cancel(){ this.isOpen = false; } leaf(obj: any){ return this.itemTextField.split('.').reduce((val, el) => val[el], obj); } itemSelected(){ if(!this.multiple){ // 重置所有选中状态 this.filtered.forEach(item => item.selected = false); // 获取当前点击的选中项 const clickedItem = this.filtered.find(item => item.selected); if(clickedItem){ clickedItem.selected = true; this.selected = [clickedItem]; // 传递副本给父组件,避免外部修改内部状态 this.selectedChanged.emit([{...clickedItem}]); } this.isOpen = false; } else { this.selected = this.filtered.filter(item => item.selected); this.selectedChanged.emit([...this.selected]); } } }
内容的提问来源于stack exchange,提问作者Evans
相关产品推荐
相关产品推荐

