rxjs combineLatest 和 Anguar2 对 Observable 数组的变化检测
rxjs combineLatest and Anguar2 change detection on Observable Arrays
我在我的应用程序中进行一些基本的更改检测时遇到了很多困难。我已经这样做了几天,失去了意志!我想知道这里是否有人可以指出正确的方向。
我有一个包含 2 个 user
列表的 group
。一个用于 members
,另一个用于 moderators
。
此方法连接到数据库以获取对象列表,每个对象都具有 $key
值。现在我想使用这些键去获取每个用户的实际 UserData
。我尝试了一切,combineLatest
似乎是我唯一可以开始工作的东西。
所以两者都调用函数,我在我的模板上使用 async
管道。
members$ = myService.getMemberListByType("blah", "members");
moderators$ = myService.getMemberListByType("blah", "moderators");
我的函数是这样的。
private getMemberListByType(groupKey: string, memberType: string) {
return this.af.database.list(`groups/${groupKey}/${memberType}`)
.switchMap(userListKeyMap => {
console.log("UserListKeyMap", memberType, userListKeyMap);
let usersObservables: Observable<UserData>[] = [];
userListKeyMap.forEach((object) => {
usersObservables.push(this.af.database.object(`users/${object.$key}`)
.map(UserData.fromJSON));
});
return Observable.combineLatest(usersObservables);
});
}
但是,Angular 没有检测到此列表的更改,如下所示。假设其他地方的不同方法删除了一个 member
并将他添加到 moderator
。在这种情况下,members
列表发出一个空的用户列表 []
。
我的 *ngIf 和 *ngFor 没有检测到这个新的空对象的变化。结果是用户(在模板上)现在是 2 个列表的一部分,但其下的数据完全准确,如日志中突出显示的那样。
我觉得当我在一个空数组上 combineLatest 时,这就是我问题的根源。什么都不能发射……那么 Angular 怎么能改变呢?我不知道如何解决它。在这个 "empty" 用例中是否有 combineLatest 的替代方案?由于我使用的是 async
管道,因此我需要一些有意义的东西。
谁能帮我解答一下我的问题?
谢谢!
更新
我确信问题出在 angular2 没有检测到空的可观察列表。特别是当 Observable Array 有一个值,但后来该值变为空时。 Angular2 不会看到空数组。我目前没有办法解决这个问题。是我的方法有误,还是需要发生一些特定的事情来注册一个空的可观察列表?
- 蓝色=组件的加载状态良好,用户是会员。
- 绿色 = 用户已从成员移至 manager/moderator。
- 红色 = 这不存在,为什么它仍然显示?
你应该尝试这样的事情:
private getMemberListByType(groupKey: string, memberType: string) {
return this.af.database.list(`groups/${groupKey}/${memberType}`)
// Force the source obs to complete to let .toArray() do its job.
.take(1)
// At this point, the obs emits a SINGLE array of ALL users.
.do(userList => console.log(userList))
// This "flattens" the array of users and emits each user individually.
.mergeMap(val => val)
// At this point, the obs emits ONE user at a time.
.do(user => console.log(user))
// Load the current user
.mergeMap(user => this.af.database.object(`users/${user.$key}`))
// At this point, the obs emits ONE loaded user at a time.
.do(userData => console.log(userData))
// Transform the raw user data into a User object.
.map(userData => User.fromJSON(userData))
// Store all user objects into a SINGLE, final array.
.toArray();
}
注意。以 do()
开头的行只是在这里解释发生了什么。无需将它们保留在最终代码中。
你的方法对我来说似乎是正确的。将 combineLatest()
与空数组一起使用不应以错误结束(类似于例如 forkJoin
,使用空数组是有效的 use-case)。然后即使它因为您使用的空数组而完成 switchMap
所以这不应该是我认为的问题。
我没有任何使用 AngularFire2 的经验,所以我不知道 this.af.database.object
是否只发出所有项目 一次然后完成 或 它在每次更改时都会发出一个值(如果我猜的话,这可能就是你想要的)。
无论如何,使用 combineLatest()
或 forkJoin()
的一个非常常见的问题是它们的源 Observables 总是必须 发出至少一项 。换句话说,如果 this.af.database.object
中的任何一个为空,那么 combineLatest()
将不会发出任何东西。
您可以确保 startWith()
总是至少有一个值。例如像下面这样:
userListKeyMap.forEach((object) => {
usersObservables.push(this.af.database.object(`users/${object.$key}`)
.startWith('{}')
.map(UserData.fromJSON));
});
如果这没有帮助,请尝试仅使用 Observable.timer()
而不是 this.af.database.object
或 this.af.database.list
以确保 Angular2 订阅它并保持订阅,即使在发出新项目时也是如此。
尝试在 observables 列表上使用 forkJoin
let userListKeyMap$ = Rx.Observable.of([{key:1}, {key:2}, {key:3}]); //mock keys
let requestUserFromKey = (key) => Rx.Observable.of({user: key}).delay(1000); //mock users
let result$ = userListKeyMap$.switchMap((userKeys) =>
Rx.Observable.forkJoin(userKeys.map(uk => requestUserFromKey(uk.key))));
result$.subscribe((users) => console.log('got some users: ', users));
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
有一个简单的解决方法:
private getMemberListByType(groupKey: string, memberType: string) {
return this.af.database.list(`groups/${groupKey}/${memberType}`)
.switchMap(userListKeyMap => {
console.log("UserListKeyMap", memberType, userListKeyMap);
let usersObservables: Observable<UserData>[] = [];
userListKeyMap.forEach((object) => {
usersObservables.push(this.af.database.object(`users/${object.$key}`)
.map(UserData.fromJSON));
});
return usersObservables.length > 0 ? Observable.combineLatest(usersObservables) : Observable.of([]);
});
}
我在我的应用程序中进行一些基本的更改检测时遇到了很多困难。我已经这样做了几天,失去了意志!我想知道这里是否有人可以指出正确的方向。
我有一个包含 2 个 user
列表的 group
。一个用于 members
,另一个用于 moderators
。
此方法连接到数据库以获取对象列表,每个对象都具有 $key
值。现在我想使用这些键去获取每个用户的实际 UserData
。我尝试了一切,combineLatest
似乎是我唯一可以开始工作的东西。
所以两者都调用函数,我在我的模板上使用 async
管道。
members$ = myService.getMemberListByType("blah", "members");
moderators$ = myService.getMemberListByType("blah", "moderators");
我的函数是这样的。
private getMemberListByType(groupKey: string, memberType: string) {
return this.af.database.list(`groups/${groupKey}/${memberType}`)
.switchMap(userListKeyMap => {
console.log("UserListKeyMap", memberType, userListKeyMap);
let usersObservables: Observable<UserData>[] = [];
userListKeyMap.forEach((object) => {
usersObservables.push(this.af.database.object(`users/${object.$key}`)
.map(UserData.fromJSON));
});
return Observable.combineLatest(usersObservables);
});
}
但是,Angular 没有检测到此列表的更改,如下所示。假设其他地方的不同方法删除了一个 member
并将他添加到 moderator
。在这种情况下,members
列表发出一个空的用户列表 []
。
我的 *ngIf 和 *ngFor 没有检测到这个新的空对象的变化。结果是用户(在模板上)现在是 2 个列表的一部分,但其下的数据完全准确,如日志中突出显示的那样。
我觉得当我在一个空数组上 combineLatest 时,这就是我问题的根源。什么都不能发射……那么 Angular 怎么能改变呢?我不知道如何解决它。在这个 "empty" 用例中是否有 combineLatest 的替代方案?由于我使用的是 async
管道,因此我需要一些有意义的东西。
谁能帮我解答一下我的问题?
谢谢!
更新
我确信问题出在 angular2 没有检测到空的可观察列表。特别是当 Observable Array 有一个值,但后来该值变为空时。 Angular2 不会看到空数组。我目前没有办法解决这个问题。是我的方法有误,还是需要发生一些特定的事情来注册一个空的可观察列表?
- 蓝色=组件的加载状态良好,用户是会员。
- 绿色 = 用户已从成员移至 manager/moderator。
- 红色 = 这不存在,为什么它仍然显示?
你应该尝试这样的事情:
private getMemberListByType(groupKey: string, memberType: string) {
return this.af.database.list(`groups/${groupKey}/${memberType}`)
// Force the source obs to complete to let .toArray() do its job.
.take(1)
// At this point, the obs emits a SINGLE array of ALL users.
.do(userList => console.log(userList))
// This "flattens" the array of users and emits each user individually.
.mergeMap(val => val)
// At this point, the obs emits ONE user at a time.
.do(user => console.log(user))
// Load the current user
.mergeMap(user => this.af.database.object(`users/${user.$key}`))
// At this point, the obs emits ONE loaded user at a time.
.do(userData => console.log(userData))
// Transform the raw user data into a User object.
.map(userData => User.fromJSON(userData))
// Store all user objects into a SINGLE, final array.
.toArray();
}
注意。以 do()
开头的行只是在这里解释发生了什么。无需将它们保留在最终代码中。
你的方法对我来说似乎是正确的。将 combineLatest()
与空数组一起使用不应以错误结束(类似于例如 forkJoin
,使用空数组是有效的 use-case)。然后即使它因为您使用的空数组而完成 switchMap
所以这不应该是我认为的问题。
我没有任何使用 AngularFire2 的经验,所以我不知道 this.af.database.object
是否只发出所有项目 一次然后完成 或 它在每次更改时都会发出一个值(如果我猜的话,这可能就是你想要的)。
无论如何,使用 combineLatest()
或 forkJoin()
的一个非常常见的问题是它们的源 Observables 总是必须 发出至少一项 。换句话说,如果 this.af.database.object
中的任何一个为空,那么 combineLatest()
将不会发出任何东西。
您可以确保 startWith()
总是至少有一个值。例如像下面这样:
userListKeyMap.forEach((object) => {
usersObservables.push(this.af.database.object(`users/${object.$key}`)
.startWith('{}')
.map(UserData.fromJSON));
});
如果这没有帮助,请尝试仅使用 Observable.timer()
而不是 this.af.database.object
或 this.af.database.list
以确保 Angular2 订阅它并保持订阅,即使在发出新项目时也是如此。
尝试在 observables 列表上使用 forkJoin
let userListKeyMap$ = Rx.Observable.of([{key:1}, {key:2}, {key:3}]); //mock keys
let requestUserFromKey = (key) => Rx.Observable.of({user: key}).delay(1000); //mock users
let result$ = userListKeyMap$.switchMap((userKeys) =>
Rx.Observable.forkJoin(userKeys.map(uk => requestUserFromKey(uk.key))));
result$.subscribe((users) => console.log('got some users: ', users));
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
有一个简单的解决方法:
private getMemberListByType(groupKey: string, memberType: string) {
return this.af.database.list(`groups/${groupKey}/${memberType}`)
.switchMap(userListKeyMap => {
console.log("UserListKeyMap", memberType, userListKeyMap);
let usersObservables: Observable<UserData>[] = [];
userListKeyMap.forEach((object) => {
usersObservables.push(this.af.database.object(`users/${object.$key}`)
.map(UserData.fromJSON));
});
return usersObservables.length > 0 ? Observable.combineLatest(usersObservables) : Observable.of([]);
});
}