[英]Angular - When receiving @Input, how to wait for other async data in the children component before executing the @Input logic
PROBLEM INTRODUCTION问题介绍
I have button-address child component which onInit loads a list of mapItems:我有按钮地址子组件,它onInit加载一个 mapItems 列表:
ngOnInit() {
this.refreshDataList();
}
protected refreshDataList(): void {
this.subscription = this.getDataList()
.pipe(
switchMap((result: AddressModel[]) => {
this.dataSource.data = [...result].map((d) => {
return {
address: d,
selected: this.selectedItems
? this.selectedItems.some(
(it) =>
it.address.pointName ===
d.pointName && it.selected
)
: false,
};
});
this.sortList(this.dataSource.data);
return this.preSelectUserPreferencesPOIs();
})
)
.subscribe();
}
protected getDataList(): Observable<AddressModel[]> {
return this.store.pipe(select(selectZoi));
}
The value of this.dataSource.data is set inside the switchMap() operator. this.dataSource.data的值在switchMap()操作符内设置。 This value is important because later the user will select elements from the list.这个值很重要,因为稍后用户将从列表中选择元素。 So I will listen for the click events from the user, find the right element of this.dataSource.data and update the selected item.所以我会监听来自用户的点击事件,找到this.dataSource.data的正确元素并更新所选项目。
My issue is that when the app inits, I also receive an @Input from another stream whose aim is to programatically select the appropiate item in the list of this.dataSource.data :我的问题是,当应用程序启动时,我还会从另一个流接收一个@Input ,其目的是以编程方式选择this.dataSource.data列表中的适当项目:
@Input()
set proximityReportPoiZoi(poiZoi: ProximityReportPoiZoi) {
this.toggleSelectedPoiZoi(poiZoi.id);
}
protected toggleSelectedPoiZoi(poiZoiId: string) {
const addressZoneSelectionModel = this.dataSource.data.find(
(item) => poiZoiId === item.address.id
);
const address = addressZoneSelectionModel.address;
if (addressZoneSelectionModel.selected) {
addressZoneSelectionModel.selected = false;
this.showHidePoiZoi(address, false);
} else {
addressZoneSelectionModel.selected = true;
this.showHidePoiZoi(address, true);
}
}
THE BUGGY LINE越野车
However, because the request to the store takes some time, when the @Input (which by the way is also an observable) is received by the component, the code inside toggleSelectedPoiZoi() cannot find the appropiate item as this.dataSource.data is still empty:但是,由于对store的请求需要一些时间,当组件接收到@Input (顺便说一下也是一个 observable)时, toggleSelectedPoiZoi() 中的代码找不到合适的项目,因为this.dataSource.data是还是空的:
const addressZoneSelectionModel = this.dataSource.data.find(
(item) => poiZoiId === item.address.id
);
THE QUESTION问题
How can I make my @Input() wait for the component to load the data for this.dataSource.data before executing this.toggleSelectedPoiZoi() ?如何让我的@Input()在执行this.toggleSelectedPoiZoi()之前等待组件加载this.dataSource.data的数据? This issue only happens during the app init.此问题仅在应用程序初始化期间发生。
THINGS I HAVE TRIED我尝试过的事情
@Input()
set proximityReportPoiZoi(poiZoi: ProximityReportPoiZoi) {
const updatePoiZoiSeletion = async () => {
await this.refreshDataList().toPromise();
this.toggleSelectedPoiZoi(poiZoi.id);
}
updatePoiZoiSeletion();
}
But it never gets to execute the following line with this.toggleSelectedPoiZoi() method.但是它永远不会使用this.toggleSelectedPoiZoi()方法执行以下行。 If I change .toPromise() for .subscribe() , the value of this.dataSource.data is still an empty array so no item can be selected.如果我将.toPromise()更改为.subscribe() ,则this.dataSource.data的值仍然是一个空数组,因此无法选择任何项目。
However no success as although I can listen for the changes on the value of the @Input , I cannot listen for changes in this.dataSource.data .但是没有成功,因为虽然我可以监听@Input值的变化,但我无法监听this.dataSource.data 的变化。
Angular complains that because the @Input is passed through the template, the value of proximityReportPoiZoi$ property has changed after parent component initialization. Angular 抱怨,因为@Input是通过模板传递的,在父组件初始化后, proximityReportPoiZoi$属性的值发生了变化。 See the parent template:查看父模板:
<app-button-address
[proximityReportPoiZoi]="(proximityReportPoiZoi$ | async)"
></app-button-address>
Any help is highly appreciated beforehand :)事先非常感谢任何帮助:)
Accd.附件to Angular life-cycle hooks event sequence, ngOnChanges
would be triggered before ngOnInit
.对于 Angular 生命周期钩子事件序列, ngOnChanges
将在ngOnInit
之前ngOnInit
。 But calling the entire subscription in the ngOnChanges
might lead to performance issues since from docs :但是在ngOnChanges
调用整个订阅可能会导致性能问题,因为来自docs :
Note that this happens very frequently, so any operation you perform here impacts performance significantly.请注意,这种情况经常发生,因此您在此处执行的任何操作都会显着影响性能。
So what you could do is use the @Input
variable inside the ngOnInit
directly in the subscription.所以你可以做的是使用@Input
内部变量ngOnInit
直接在订阅。
export someComponent implements OnInit {
_poiZoi: ProximityReportPoiZoi;
@Input()
set proximityReportPoiZoi(poiZoi: ProximityReportPoiZoi) {
this._poiZoi = poiZoi;
}
ngOnInit() {
this.refreshDataList();
}
protected refreshDataList(): void {
this.subscription = this.getDataList().pipe(
switchMap((result: AddressModel[]) => {
this.dataSource.data = [...result].map((d) => {
return {
address: d,
selected: this.selectedItems
? this.selectedItems.some((it) =>
it.address.pointName === d.pointName && it.selected
)
: false,
};
});
this.sortList(this.dataSource.data);
this.preSelectUserPreferencesPOIs();
}),
map(() => this._poiZoi.id)
)
.subscribe({
next: (poiZoiId: any) => this.toggleSelectedPoiZoi(poiZoi.id),
error: (error: any) => console.log(error)
});
}
}
I propose an approach based on pure RxJs logic which does not relay on the Angular lifecycle methods.我提出了一种基于纯 RxJs 逻辑的方法,它不依赖于 Angular 生命周期方法。
If I understand right, you have 2 streams at play如果我理解正确,你有 2 个流在玩
this.getDataList()
and whose notification is used to set this.dataSource.data
- let's call this stream obs_1其源为this.getDataList()
且其通知用于设置this.dataSource.data
的流 - 我们将此流称为 obs_1proximityReportPoiZoi
Input and which is used to select the the initial value of the list of values - let's call this stream obs_2发出值的流,该值传递给proximityReportPoiZoi
报告PoiZoi输入并用于选择值列表的初始值 - 我们将此流称为 obs_2The problem here is to make sure that we do the following steps in strict temporal order这里的问题是确保我们按照严格的时间顺序执行以下步骤
subscribe
to obs_1 and process its notification subscribe
obs_1并处理其通知 In terms of pure rxJs logic, this is a good case for using concatMap
.就纯 rxJs 逻辑而言,这是使用concatMap
的好例子。
The logic could look like this逻辑可能是这样的
obs_2.pipe(
concatMap(res_2 => obs_1.pipe(
map(res_1 => ([res_1, res_2))
)
)
).subscribe(
([addressModelArray, poiZoi]) => {
setDataSourceData(addressModelArray) // i.e. the logic in switchMap
toggleSelectedPoiZoi(poiZoi.id);
}
)
You can try to implement this approach within your button-address
Component.您可以尝试在button-address
组件中实现这种方法。 This could be accomplished implementing obs_2 as a Subject
which emits within the proximityReportPoiZoi
set method.这可以通过将obs_2实现为在proximityReportPoiZoi
PoiZoi设置方法内发出的Subject
来实现。
The approach I would use though is to try to isolate all this logic into a service
which is injected into the button-address
Component.我想,虽然使用的方法是尝试所有这一切的逻辑隔离到一个service
被注入到button-address
组件。 This service
knows both obs_1 and obs_2 and therefore can implement this logic in the pipe
and expose it as a public API Observable, something like this该service
知道obs_1和obs_2 ,因此可以在pipe
实现此逻辑并将其公开为公共 API Observable,类似这样
public listAndSelectedVal$ = obs_2.pipe(
concatMap(res_2 => obs_1.pipe(
// apply some logic to filter the default value
filter(res_1 => res_1.length > 0),
map(res_1 => ([res_1, res_2))
)
)
)
button-address
Component would need to subscribe tp listAndSelectedVal$
to perform its job. button-address
组件需要订阅 tp listAndSelectedVal$
才能执行其工作。
It is possible also to explore the use of combineLatest
in case obs_1 and obs_2 can emit more than once before completing.也可以探索combineLatest
的使用,以防obs_1和obs_2在完成之前可以发出多次。
Such an approach would make testing much easier, since testing a service is simpler than testing a Component.这种方法会使测试更容易,因为测试服务比测试组件更简单。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.