Angular2 子组件在 ngFor 中意外销毁
Angular2 Child component destroyed unexpectedly in ngFor
所以我有一个来自 @Output
事件的组件,该事件在提交时触发,如下所示:
@Component({
selector: 'some-component',
templateUrl: './SomeComponent.html'
})
export class SomeComponent{
@Input() data: any;
@Output() onSubmit: EventEmitter<void> = new EventEmitter<void>();
constructor(private someService: SomeService) {}
submitForm(): void{
this.someService.updateBackend(this.data, ()=>{
this.onSubmit.emit();
});
}
}
我正在使用 ngFor
创建此组件的多个元素:
<template let-data ngFor [ngForOf]="dataCollection">
<some-component [data]="data" (onSubmit)="doSomthing()"></some-component>
</template>
最后缺少的部分是提交时使用的服务:
@Injectable()
export class SomeService{
constructor() {}
updateBackend(data: any, callback: () => void): void{
/*
* updating the backend
*/.then((result) => {
const { errors, data } = result;
if (data) {
callback();
}
})
}
}
在 submitForm()
函数的开头,this.onSubmit.observers
是一个包含一个观察者的数组,这是应该的。
一旦到达调用 this.onSubmit.emit()
的回调方法,this.onSubmit.observers
就是一个包含零个观察者的数组。
我遇到了两种非常奇怪的行为:
- 如果我在
SomeService.updateBackend
中删除更新后端的实际调用它工作得很好,并且 observers
仍然是一个包含一个观察者的数组!
- 如果我保持对后端的实际调用但不使用
ngFor
并且只显示一个 <some-element>
它也可以正常工作,在回调中将一个观察者保留在 this.onSubmit.observers
中范围!
知道我做错了什么吗?
提前致谢!
更新:
感谢@StevenLuke 关于记录 SomeComponent
的 ngOnDestroy
的评论,我发现它在发出之前就被销毁了。
实际上,SomeService.updateBackend
完成后它做的第一件事就是销毁该组件的所有实例并重新创建它们!
这就是让观察者改变的原因!为什么会这样?
感谢@GünterZöchbauer 的评论,我发现情况是 ngFor
绑定到的数据在我更新后端时被新实例替换,因此,它重新呈现它的子组件导致重新初始化(destory + init) ,这使得组件的实例被覆盖。
为了解决这个问题,我不得不将 dataCollection
放在一个单独的服务中,为父组件 ngOnInit
获取它,避免它导致 [=10= 重新渲染], 子组件执行结束后才重新获取其数据
希望对大家有所帮助!
如果您在 *ngFor 中提供一个 trackBy 函数来识别 dataCollection 中的项目,它不会销毁和初始化。您的模板将是:
<some-component *ngFor="let data of dataCollection;trackBy:trackByFunction"
[data]="data" (onSubmit)="doSomthing()"></some-component>
trackByFunction 看起来像:
trackByFunction(index, item) {
return item ? item.id : undefined;
}
因此即使您的 dataCollection 中的项目是新对象,如果它的 id 与先前集合中的 id 匹配,*ngFor 将更新 [data] 但不会销毁和初始化组件。
所以我有一个来自 @Output
事件的组件,该事件在提交时触发,如下所示:
@Component({
selector: 'some-component',
templateUrl: './SomeComponent.html'
})
export class SomeComponent{
@Input() data: any;
@Output() onSubmit: EventEmitter<void> = new EventEmitter<void>();
constructor(private someService: SomeService) {}
submitForm(): void{
this.someService.updateBackend(this.data, ()=>{
this.onSubmit.emit();
});
}
}
我正在使用 ngFor
创建此组件的多个元素:
<template let-data ngFor [ngForOf]="dataCollection">
<some-component [data]="data" (onSubmit)="doSomthing()"></some-component>
</template>
最后缺少的部分是提交时使用的服务:
@Injectable()
export class SomeService{
constructor() {}
updateBackend(data: any, callback: () => void): void{
/*
* updating the backend
*/.then((result) => {
const { errors, data } = result;
if (data) {
callback();
}
})
}
}
在 submitForm()
函数的开头,this.onSubmit.observers
是一个包含一个观察者的数组,这是应该的。
一旦到达调用 this.onSubmit.emit()
的回调方法,this.onSubmit.observers
就是一个包含零个观察者的数组。
我遇到了两种非常奇怪的行为:
- 如果我在
SomeService.updateBackend
中删除更新后端的实际调用它工作得很好,并且observers
仍然是一个包含一个观察者的数组! - 如果我保持对后端的实际调用但不使用
ngFor
并且只显示一个<some-element>
它也可以正常工作,在回调中将一个观察者保留在this.onSubmit.observers
中范围!
知道我做错了什么吗?
提前致谢!
更新:
感谢@StevenLuke 关于记录 SomeComponent
的 ngOnDestroy
的评论,我发现它在发出之前就被销毁了。
实际上,SomeService.updateBackend
完成后它做的第一件事就是销毁该组件的所有实例并重新创建它们!
这就是让观察者改变的原因!为什么会这样?
感谢@GünterZöchbauer 的评论,我发现情况是 ngFor
绑定到的数据在我更新后端时被新实例替换,因此,它重新呈现它的子组件导致重新初始化(destory + init) ,这使得组件的实例被覆盖。
为了解决这个问题,我不得不将 dataCollection
放在一个单独的服务中,为父组件 ngOnInit
获取它,避免它导致 [=10= 重新渲染], 子组件执行结束后才重新获取其数据
希望对大家有所帮助!
如果您在 *ngFor 中提供一个 trackBy 函数来识别 dataCollection 中的项目,它不会销毁和初始化。您的模板将是:
<some-component *ngFor="let data of dataCollection;trackBy:trackByFunction"
[data]="data" (onSubmit)="doSomthing()"></some-component>
trackByFunction 看起来像:
trackByFunction(index, item) {
return item ? item.id : undefined;
}
因此即使您的 dataCollection 中的项目是新对象,如果它的 id 与先前集合中的 id 匹配,*ngFor 将更新 [data] 但不会销毁和初始化组件。