angular: 在模板中使用异步管道绑定方法 + 参数
angular: bind method + param with async pipe in template
我正在玩 firebase
和多对多中间人收集模式。参见:https://angularfirebase.com/lessons/firestore-nosql-data-modeling-by-example/#Many-to-Many-Middle-Man-Collection
但是在我获得集合中的 id 数组后,我正在努力寻找 "complete the join" 的模式。
我想使用 async pipe
在模板中呈现 'joined' 元素,如下所示:
<div *ngFor="let id of itemIds">
<div *ngIf="(getById$(id) | async) as item">{{item.name}}</div>
</div>
但是 Angular change detection
调用 getById$()
多次,我每次都得到一个新的 Observable,然后...浏览器冻结。
我通过缓存 Observable 破解了一个解决方案,但这似乎不对。
getById$(uuid:string):Observable<T>{
this['get$_'] = this['get$_'] || {}; // cache in component
const cached$ = this['get$_'][uuid];
if (cached$) return cached$ as Observable<T>;
return this['get$_'][uuid] = this.get(uuid) as Observable<T>;
}
还有更好的模式吗?
让我们理智地进行游戏。 *ngFor
集合中使用的任何项目 都必须保持不变 以防止更改检测重新呈现项目视图,从而防止重新订阅可观察对象。因此,你必须在某处保存一组可观察对象,而不是即时创建它们。您完成它的方式几乎就是您实际需要的。我们可以改进它,虽然不是很多。
当您获得 itemIds
集合时,您需要执行一次将它们映射到某个包含可观察对象的集合,并将该集合放入模板中,而不仅仅是源 ID。像这样:
private _items: any[] = []; // put your type here instead of <any>
get items$(): any[] {
return this._items;
}
set itemIds(value: any[]) {
if (!value) {
this._items = [];
return;
}
// I know that it's pretty ugly and inefficient implementation,
// but it'll do for explaining the idea.
this._items = value.map(id => {
// this is the essential part: if observable had already been created for the id
// then you _must_ preserve the same object instance in order to make change detection happy
// you can also try to use trackBy function in the *ngFor definition
// but I think that it will not help you in this case
let item$ = this._items.find(i => i.id === id);
if (!item$) {
item$ = {
id: id,
obs$: this.getById(id)
};
}
return item$;
});
}
然后在模板中:
<div *ngFor="let item$ of items$">
<div *ngIf="(item$.obs$ | async) as item">{{item.name}}</div>
</div>
这样,可观察对象的各自 ID 将保持不变,并且整个过程将通过更改检测正常工作。
因此,总结一下:您需要保留一个可观察对象集合并仅在 ID 的源集合发生变化时更新它,并且您需要为为其创建的 ID 保留可观察实例。
我正在玩 firebase
和多对多中间人收集模式。参见:https://angularfirebase.com/lessons/firestore-nosql-data-modeling-by-example/#Many-to-Many-Middle-Man-Collection
但是在我获得集合中的 id 数组后,我正在努力寻找 "complete the join" 的模式。
我想使用 async pipe
在模板中呈现 'joined' 元素,如下所示:
<div *ngFor="let id of itemIds">
<div *ngIf="(getById$(id) | async) as item">{{item.name}}</div>
</div>
但是 Angular change detection
调用 getById$()
多次,我每次都得到一个新的 Observable,然后...浏览器冻结。
我通过缓存 Observable 破解了一个解决方案,但这似乎不对。
getById$(uuid:string):Observable<T>{
this['get$_'] = this['get$_'] || {}; // cache in component
const cached$ = this['get$_'][uuid];
if (cached$) return cached$ as Observable<T>;
return this['get$_'][uuid] = this.get(uuid) as Observable<T>;
}
还有更好的模式吗?
让我们理智地进行游戏。 *ngFor
集合中使用的任何项目 都必须保持不变 以防止更改检测重新呈现项目视图,从而防止重新订阅可观察对象。因此,你必须在某处保存一组可观察对象,而不是即时创建它们。您完成它的方式几乎就是您实际需要的。我们可以改进它,虽然不是很多。
当您获得 itemIds
集合时,您需要执行一次将它们映射到某个包含可观察对象的集合,并将该集合放入模板中,而不仅仅是源 ID。像这样:
private _items: any[] = []; // put your type here instead of <any>
get items$(): any[] {
return this._items;
}
set itemIds(value: any[]) {
if (!value) {
this._items = [];
return;
}
// I know that it's pretty ugly and inefficient implementation,
// but it'll do for explaining the idea.
this._items = value.map(id => {
// this is the essential part: if observable had already been created for the id
// then you _must_ preserve the same object instance in order to make change detection happy
// you can also try to use trackBy function in the *ngFor definition
// but I think that it will not help you in this case
let item$ = this._items.find(i => i.id === id);
if (!item$) {
item$ = {
id: id,
obs$: this.getById(id)
};
}
return item$;
});
}
然后在模板中:
<div *ngFor="let item$ of items$">
<div *ngIf="(item$.obs$ | async) as item">{{item.name}}</div>
</div>
这样,可观察对象的各自 ID 将保持不变,并且整个过程将通过更改检测正常工作。
因此,总结一下:您需要保留一个可观察对象集合并仅在 ID 的源集合发生变化时更新它,并且您需要为为其创建的 ID 保留可观察实例。