英文:
Combining two observable sources filtering by one first observable property
问题
以下是代码的翻译部分:
有一个可观察对象,发出一个用户列表,内容如下:
[{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}]
还有另一个可观察对象返回扩展的用户数据:
{
"id": 1,
"authorizations": 20
}
我在特定的详细信息页面中使用每个用户的详细信息,但我希望将用户列表中的一部分详细信息合并起来,并只按状态“Active”筛选:
[{
"id": 1,
"name": "John",
"status": "Active",
"authorizations": 20
},
{
"id": 4,
"name": "Susan",
"status": "Active",
"authorizations": 10
}]
是否可以使用一些过滤操作符来合并这些结果,而不必使用两个订阅?
尝试了以下代码,但是否有更好或更简化的方法来实现?
import { of, Observable, combineLatest } from 'rxjs';
import { filter, map, mergeAll, mergeMap } from 'rxjs/operators';
类型 State = 'Active' | 'Inactive';
类型 User = { id: number; name: string; status: State };
类型 UserDetail = { id: number; authorizations: number };
类型 UserWithAuthorizations = User & UserDetail
const users: User[] = [
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
const authorizations: UserDetail[] = [
{ id: 1, authorizations: 20 },
{ id: 2, authorizations: 5 },
{ id: 3, authorizations: 30 },
{ id: 4, authorizations: 10 },
];
const getAuthorizationsByUser= (userId: number): Observable<Partial<UserWithAuthorizations>> => {
const users$ = of(users)
const authorizations$ = of(authorizations)
return combineLatest([users$, authorizations$]).pipe(
map(res => {
const user = res[0].find(u => u.id === userId)
const { authorizations } = res[1].find(a => a.id === userId)
return {
...user,
authorizations
}
}))
};
const fetchUsersWithAuthorizations = () => of(users);
fetchUsersWithAuthorizations()
.pipe(
mergeAll<User>(),
filter((user) => user.status === "Active"),
mergeMap((user) => getAuthorizationsByUser(user.id))
)
.subscribe(console.log);
请注意,这个翻译部分只包括代码和注释的内容。
英文:
Having an observable emitting a list of users with the next content:
[
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
And I have another observable returning the extended user data:
{
"id": 1,
"authorizations: 20
}
I use the detail of each user in an specific details page, but I would like to combine part of the detail in the users list and obtain the next result and only filter by the status Active:
[
{
"id": 1,
"name": "John",
"status": "Active",
"authorizations": 20
},
{
"id": 4,
"name": "Susan",
"status": "Active",
"authorizations": 10
}
]
It is possible to use some filtering operator and combine those results without use two subscriptions?
Tried the following code but, would be a better or simplified way to do it?
import { of, Observable, combineLatest } from 'rxjs';
import { filter, map, mergeAll, mergeMap } from 'rxjs/operators';
type State = 'Active' | 'Inactive';
type User = { id: number; name: string; status: State };
type UserDetail = { id: number; authorizations: number };
type UserWithAuthorizations = User & UserDetail
const users: User[] = [
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
const authorizations: UserDetail[] = [
{ id: 1, authorizations: 20 },
{ id: 2, authorizations: 5 },
{ id: 3, authorizations: 30 },
{ id: 4, authorizations: 10 },
];
const getAuthorizationsByUser= (userId: number): Observable<Partial<UserWithAuthorizations>> => {
const users$ = of(users)
const authorizations$ = of(authorizations)
return combineLatest([users$, authorizations$]).pipe(
map(res => {
const user = res[0].find(u => u.id === userId)
const { authorizations } = res[1].find(a => a.id === userId)
return {
...user,
authorizations
}
}))
};
const fetchUsersWithAuthorizations = () => of(users);
fetchUsersWithAuthorizations()
.pipe(
mergeAll<User>(),
filter((user) => user.status === "Active"),
mergeMap((user) => getAuthorizationsByUser(user.id))
)
.subscribe(console.log);
答案1
得分: 1
我认为您想要的是以下内容:
// 将数组转换为基于某个键的字典的实用工具
// 我将在一会儿解释原因
const toDictionary = <
Obj extends Record<string | number | symbol, any>,
Key extends keyof Obj
>(
arr: Obj[],
key: Key
): Record<Obj[Key], Obj> =>
arr.reduce((acc, curr) => {
acc[curr[key]] = curr;
return acc;
}, {} as Record<Obj[Key], Obj>);
const fetchUsers = () => of(users);
const fetchAuthorizations = () => of(authorizations);
combineLatest([
fetchUsers(),
fetchAuthorizations().pipe(
// 在这里,我们将授权数组转换为字典
// 以ID为键,以便以后我们可以根据ID访问授权
// 这将节省我们对授权数组进行迭代
// 对于每个用户,这更加高效。请注意,我们也在这里执行它,而不是稍后执行它
// 这样,如果`fetchUsers()`被替换为可以多次发出的可观察对象,我们不会进行转换
// 我们只在必要时才执行它:仅当授权可观察对象发生更改时
map((authorizations) => toDictionary(authorizations, 'id'))
),
])
.pipe(
map(([users, authorizations]): UserWithAuthorizations[] =>
users
.map((user) => ({
...user,
authorizations: authorizations[user.id]?.authorizations,
}))
.filter((user) => user.status === 'Active')
)
)
.subscribe(console.log);
在这个代码片段中,我将代码中的注释翻译成中文。如果您有任何其他问题或需要进一步的帮助,请随时告诉我。
英文:
I think what you're after is the following:
// utility to transform an array into a dictionary based on some key
// I'll explain why in a second
const toDictionary = <
Obj extends Record<string | number | symbol, any>,
Key extends keyof Obj
>(
arr: Obj[],
key: Key
): Record<Obj[Key], Obj> =>
arr.reduce((acc, curr) => {
acc[curr[key]] = curr;
return acc;
}, {} as Record<Obj[Key], Obj>);
const fetchUsers = () => of(users);
const fetchAuthorizations = () => of(authorizations);
combineLatest([
fetchUsers(),
fetchAuthorizations().pipe(
// here, we transform the authorizations array into a dictionary
// with the ID as key, so that later on we can access an authorization
// based on an ID. This will save us to iterate on the authorization array
// for each of the users which is more efficient. Note that we also do it
// here instead of doing it later so that if the `fetchUsers()` was to be replaced by an observable
// that can emit multiple times, we'd not make that transformation if it emits
// we only do it when necessary: Only when the authorizations observable changes
map((authorizations) => toDictionary(authorizations, 'id'))
),
])
.pipe(
map(([users, authorizations]): UserWithAuthorizations[] =>
users
.map((user) => ({
...user,
authorizations: authorizations[user.id]?.authorizations,
}))
.filter((user) => user.status === 'Active')
)
)
.subscribe(console.log);
See a live demo here.
EDIT: I saw your comment on the post only after answering the above.
Based on the fact that you can only fetch authorizations
individually, I've got 2 options for you:
- You want only 1 emission, including all the users with their permissions
const usersWithAUthorizations$: Observable<UserWithAuthorizations[]> =
fetchUsers().pipe(
mergeMap((users) =>
forkJoin(
users
.filter((user) => user.status === 'Active')
.map((user) =>
fetchAuthorizations(user.id).pipe(
map((authorizations) => ({
...user,
...authorizations,
}))
)
)
)
)
);
- You want to get all the intermediate emission, meaning that each time we get the authorization for a given user, we'd emit a temporary result with all the previous responses + the current, etc until we treated all of them
const usersWithAUthorizations$: Observable<UserWithAuthorizations[]> =
fetchUsers().pipe(
mergeMap((users) =>
users
.filter((user) => user.status === 'Active')
.map((user) =>
fetchAuthorizations(user.id).pipe(
map((authorizations) => ({
...user,
...authorizations,
}))
)
)
),
mergeAll(),
scan((acc, curr) => [...acc, curr], [] as UserWithAuthorizations[])
);
答案2
得分: 0
为什么不在单个combineLatest
中完成所有操作?
const { of, map, combineLatest } = rxjs;
const users = [
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
const authorizations = [
{ id: 1, authorizations: 20 },
{ id: 2, authorizations: 5 },
{ id: 3, authorizations: 30 },
{ id: 4, authorizations: 10 },
];
const users$ = of(users)
const authorizations$ = of(authorizations)
const activeUsersWithAuthorizations$ = combineLatest([users$, authorizations$]).pipe(
map(([users, authorizations]) =>
users
.filter((user) => user.status === 'Active')
.map((user) => ({
...user,
authorizations: authorizations.find((a) => a.id === user.id)?.authorizations,
}))
)
);
activeUsersWithAuthorizations$.subscribe(activeUsersWithAuthorizations => {
console.log(activeUsersWithAuthorizations);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js" integrity="sha512-v0/YVjBcbjLN6scjmmJN+h86koeB7JhY4/2YeyA5l+rTdtKLv0VbDBNJ32rxJpsaW1QGMd1Z16lsLOSGI38Rbg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
英文:
Why not do it all in a single combine latest?
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const { of, map, combineLatest } = rxjs;
const users = [
{
"id": 1,
"name": "John",
"status": "Active"
},
{
"id": 2,
"name": "Mary",
"status": "Inactive"
},
{
"id": 3,
"name": "Peter",
"status": "Inactive"
},
{
"id": 4,
"name": "Susan",
"status": "Active"
}
]
const authorizations = [
{ id: 1, authorizations: 20 },
{ id: 2, authorizations: 5 },
{ id: 3, authorizations: 30 },
{ id: 4, authorizations: 10 },
];
const users$ = of(users)
const authorizations$ = of(authorizations)
const activeUsersWithAuthorizations$ = combineLatest([users$, authorizations$]).pipe(
map(([users, authorizations]) =>
users
.filter((user) => user.status === 'Active')
.map((user) => ({
...user,
authorizations: authorizations.find((a) => a.id === user.id)?.authorizations,
}))
)
);
activeUsersWithAuthorizations$.subscribe(activeUsersWithAuthorizations => {
console.log(activeUsersWithAuthorizations);
});
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js" integrity="sha512-v0/YVjBcbjLN6scjmmJN+h86koeB7JhY4/2YeyA5l+rTdtKLv0VbDBNJ32rxJpsaW1QGMd1Z16lsLOSGI38Rbg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论