下面的代码轻松地说明了我要使用2种模型进行的操作。假设我们有一个Book
模型和一个Author
模型。首先,我们使用返回类型Books
的服务来获取Observable<Book>
的列表。然后,我们使用*ngFor
遍历每本书,但我们也想在本书旁边显示Author
,因此在我们的迭代中,我们有一些类似以下的HTML:
<span>{{ getAuthorName(book) | async }}</span>
getAuthorName
有自己的请求,在AuthorService
上发出,要求ID
提取作者。问题在于,当我们在此处返回Observable<string>
时,浏览器完全崩溃,因为更改检测运行不正常,原因是每个摘要周期都返回了一个“新”可观察值,这导致更改检测再次无休止地运行,对吧?
下面的代码可以轻松地放入StackBlitz中并进行复制(我没有链接到它,因为它炸毁了您的浏览器CPU)
<ul>
<li *ngFor="let item of getData() | async">
{{ getOtherData(item.id) | async }}
</li>
</ul>
import { Component } from '@angular/core';
import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
getData() {
return of([{
id: 1
},{
id: 2
},{
id: 3
}]);
}
getOtherData(id: number) {
const data = of([{
id: 1,
name: 'Matt'
},{
id: 2,
name: 'Steve'
},{
id: 3,
name: 'Alice'
}]);
return data.pipe(
map(x => x.find(d => d.id == id))
)
}
}
很明显,可以在此处完成一些优化,例如Authors的缓存等,但是总的来说,在RXJS的世界中应该如何处理这种情况?我知道有用于处理诸如Akita和ngrx之类的状态的整个框架,但是如果我不想处理所有这些而只想用原始rxjs
来构建我的应用程序该怎么办?该问题的预期解决方案是什么?您是否使用其他rxjs运算符阻止这种情况的发生? (由于每次每次都会返回一个新的Obserable
,所以我找不到帮助它的东西)您是否只需要使用视图模型而不是方法?这样,它们始终只是可观察对象的单一参考吗? (例如,BookAuthorViewModel
具有一个$author
属性,该属性在我们将视图迭代到单个可观察对象之前被设置吗?
我还没有找到一个“感觉不错”的解决方案,我觉得我被迫使用这些状态管理框架只是为了做这样的简单事情。
答案 0 :(得分:2)
您不应在视图中调用方法。如果您有一个返回可观察值的方法,那么每次更改保留检查值时,都会创建一个新的可观察值。
data$ = of([{
id: 1
},{
id: 2
},{
id: 3
}]);
otherData$ = of([{
id: 1,
name: 'Matt'
},{
id: 2,
name: 'Steve'
},{
id: 3,
name: 'Alice'
}]);
然后绑定到可观察的
<li *ngFor="let item of data$ | async">
{{ otherData$ | async | find: item.id }}
</li>
并创建查找管道
@Pipe({
name: 'find'
})
export class FindPipe implements PipeTransform {
transform(options, id): string {
return options.find(option => option.id === id);
}
}
StackBlitz https://stackblitz.com/edit/angular-6vgosc
答案 1 :(得分:2)
出于性能原因,您不应在html模板中使用函数。我建议先合并数据,然后再在模板中使用它。看起来可能像这样:
this.books$ = this.myService.getBooks();
this.authors$ = this.myService.getAuthors();
this.bookAuthorPairs$ = combineLatest([this.books$, this.authors$]).pipe(
map(([books, authors]) => books.map(
(book) => {
return { book, author: authors.find(author => book.authorId === author.id) };
}
)),
);
答案 2 :(得分:1)
另一种选择是合并服务中的流。
对于每本书,您都需要作者的名字。在我的示例中,对于每个ToDo,它都会获取用户名。
todos$ = this.http
.get<ToDo[]>(this.todoUrl)
.pipe(catchError(err => throwError("Error occurred")));
todosWithUser$ = this.todos$.pipe(
mergeMap(todos =>
forkJoin(
todos.map(todo =>
this.http.get<User>(`${this.userUrl}/${todo.userId}`).pipe(
map(user =>({
...todo,
usersName: user.name
} as ToDo)
)
)
)
)
)
);
https://stackblitz.com/edit/angular-users-todos-deborahk
todos$
流会获取所有待办事项(或您的情况下的书籍)。
它使用mergeMap
映射并合并列表中的每个ToDo。这是自动订阅可检索用户的“内部” Observable所必需的。
forkJoin
允许我们处理ToDo的数组并为每个数组找到用户。然后将它们全部重新组合成一个数组。
最后一个map
将usersName
属性添加到每个Todo。