通过一个第一个可观察属性过滤,将两个可观察源组合在一起。

huangapple go评论48阅读模式
英文:

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:

[
{
&quot;id&quot;: 1,
&quot;name&quot;: &quot;John&quot;,
&quot;status&quot;: &quot;Active&quot;
},
{
&quot;id&quot;: 2,
&quot;name&quot;: &quot;Mary&quot;,
&quot;status&quot;: &quot;Inactive&quot;
},
{
&quot;id&quot;: 3,
&quot;name&quot;: &quot;Peter&quot;,
&quot;status&quot;: &quot;Inactive&quot;
},
{
&quot;id&quot;: 4,
&quot;name&quot;: &quot;Susan&quot;,
&quot;status&quot;: &quot;Active&quot;
}
]

And I have another observable returning the extended user data:

{
&quot;id&quot;: 1,
&quot;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:

[
{
&quot;id&quot;: 1,
&quot;name&quot;: &quot;John&quot;,
&quot;status&quot;: &quot;Active&quot;,
&quot;authorizations&quot;: 20
},
{
&quot;id&quot;: 4,
&quot;name&quot;: &quot;Susan&quot;,
&quot;status&quot;: &quot;Active&quot;,
&quot;authorizations&quot;: 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 &#39;rxjs&#39;;
import { filter, map, mergeAll, mergeMap } from &#39;rxjs/operators&#39;;
type State = &#39;Active&#39; | &#39;Inactive&#39;;
type User = { id: number; name: string; status: State };
type UserDetail = { id: number; authorizations: number };
type UserWithAuthorizations = User &amp; UserDetail
const users: User[] = [
{
&quot;id&quot;: 1,
&quot;name&quot;: &quot;John&quot;,
&quot;status&quot;: &quot;Active&quot;
},
{
&quot;id&quot;: 2,
&quot;name&quot;: &quot;Mary&quot;,
&quot;status&quot;: &quot;Inactive&quot;
},
{
&quot;id&quot;: 3,
&quot;name&quot;: &quot;Peter&quot;,
&quot;status&quot;: &quot;Inactive&quot;
},
{
&quot;id&quot;: 4,
&quot;name&quot;: &quot;Susan&quot;,
&quot;status&quot;: &quot;Active&quot;
}
]
const authorizations: UserDetail[] = [
{ id: 1, authorizations: 20 },
{ id: 2, authorizations: 5 },
{ id: 3, authorizations: 30 },
{ id: 4, authorizations: 10 },
];
const getAuthorizationsByUser= (userId: number): Observable&lt;Partial&lt;UserWithAuthorizations&gt;&gt;  =&gt; {
const users$ = of(users)
const authorizations$ = of(authorizations)
return combineLatest([users$, authorizations$]).pipe(
map(res =&gt; {
const user = res[0].find(u =&gt; u.id === userId)
const { authorizations } = res[1].find(a =&gt; a.id === userId)
return {
...user,
authorizations
}
}))
};
const fetchUsersWithAuthorizations = () =&gt; of(users);
fetchUsersWithAuthorizations()
.pipe(
mergeAll&lt;User&gt;(),
filter((user) =&gt; user.status === &quot;Active&quot;),
mergeMap((user) =&gt; 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&#39;ll explain why in a second
const toDictionary = &lt;
  Obj extends Record&lt;string | number | symbol, any&gt;,
  Key extends keyof Obj
&gt;(
  arr: Obj[],
  key: Key
): Record&lt;Obj[Key], Obj&gt; =&gt;
  arr.reduce((acc, curr) =&gt; {
    acc[curr[key]] = curr;
    return acc;
  }, {} as Record&lt;Obj[Key], Obj&gt;);

const fetchUsers = () =&gt; of(users);
const fetchAuthorizations = () =&gt; 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&#39;d not make that transformation if it emits
    // we only do it when necessary: Only when the authorizations observable changes
    map((authorizations) =&gt; toDictionary(authorizations, &#39;id&#39;))
  ),
])
  .pipe(
    map(([users, authorizations]): UserWithAuthorizations[] =&gt;
      users
        .map((user) =&gt; ({
          ...user,
          authorizations: authorizations[user.id]?.authorizations,
        }))
        .filter((user) =&gt; user.status === &#39;Active&#39;)
    )
  )
  .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:

  1. You want only 1 emission, including all the users with their permissions
const usersWithAUthorizations$: Observable&lt;UserWithAuthorizations[]&gt; =
  fetchUsers().pipe(
    mergeMap((users) =&gt;
      forkJoin(
        users
          .filter((user) =&gt; user.status === &#39;Active&#39;)
          .map((user) =&gt;
            fetchAuthorizations(user.id).pipe(
              map((authorizations) =&gt; ({
                ...user,
                ...authorizations,
              }))
            )
          )
      )
    )
  );

Live demo

  1. 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&lt;UserWithAuthorizations[]&gt; =
  fetchUsers().pipe(
    mergeMap((users) =&gt;
      users
        .filter((user) =&gt; user.status === &#39;Active&#39;)
        .map((user) =&gt;
          fetchAuthorizations(user.id).pipe(
            map((authorizations) =&gt; ({
              ...user,
              ...authorizations,
            }))
          )
        )
    ),
    mergeAll(),
    scan((acc, curr) =&gt; [...acc, curr], [] as UserWithAuthorizations[])
  );

Live demo

答案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 = [
{
&quot;id&quot;: 1,
&quot;name&quot;: &quot;John&quot;,
&quot;status&quot;: &quot;Active&quot;
},
{
&quot;id&quot;: 2,
&quot;name&quot;: &quot;Mary&quot;,
&quot;status&quot;: &quot;Inactive&quot;
},
{
&quot;id&quot;: 3,
&quot;name&quot;: &quot;Peter&quot;,
&quot;status&quot;: &quot;Inactive&quot;
},
{
&quot;id&quot;: 4,
&quot;name&quot;: &quot;Susan&quot;,
&quot;status&quot;: &quot;Active&quot;
}
]
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]) =&gt;
users
.filter((user) =&gt; user.status === &#39;Active&#39;)
.map((user) =&gt; ({
...user,
authorizations: authorizations.find((a) =&gt; a.id === user.id)?.authorizations,
}))
)
);
activeUsersWithAuthorizations$.subscribe(activeUsersWithAuthorizations =&gt; {
console.log(activeUsersWithAuthorizations);
});

<!-- language: lang-html -->

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js&quot; integrity=&quot;sha512-v0/YVjBcbjLN6scjmmJN+h86koeB7JhY4/2YeyA5l+rTdtKLv0VbDBNJ32rxJpsaW1QGMd1Z16lsLOSGI38Rbg==&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年2月10日 04:32:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/75404139.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定