如何从封装在服务中的流中获取已检查的用户?

How to get checked users from stream incapsulated in service?

有一个服务UsersService包含用户逻辑:

interaface User {
    id: number,
    name: string,
    checked?: boolean;
}

class UserService {
   public users$ = new BehaviorSubject<User[]>([]);
   public load() {
       this.http.get('users').subscribe((users: Users[] => this.users$.next(users));
   }
   
   public get users() {
       return this.users$.asObservable();
   }
}

我从组件内部的服务获取数据:

public users$: Observable<User[]>;
constructor(private userService: UserService) {
    this.users$ = this.userService.users;
}

模板是:

<div> *ngFor="let user of users$ | async">
   <div>{{ user.name }}</div>
   <div><mat-checkbox [(ngModel)]="user.checked"></mat-checkbox></div>
</div>
<div>Checked users: {{ checkedusers | json }}</div>

如何在更新一个复选框或所有复选框后使用 rxjs 方法{{ checkedusers | json }}从流中获取所有选中的用户并将其封装到服务中?

使用 scan 运算符将所有可以修改用户列表的源合并为一个流。然后在组件中,不是直接修改用户对象,而是将更改发送到用于修改列表的主题之一:

服务

class UserService {
  readonly loadUsersSubject = new Subject();
  readonly userChangeSubject = new Subject<[userId: number, Partial<User>]>();

  readonly users$ = combineLatest([
    this.loadUsersSubject.pipe(
      switchMap(() => this.http.get('users')),
      map((users) => (s) => users))
    ),
    this.userChangeSubject.pipe(map(([userId, partialUser]) => (s) => {
      const index = s.findIndex(x => x.userId === userId);
      return [...s.slice(0, index - 1), { ... s[index], partialUser }, ...s.slice(index + 1)]
    })
  ]).pipe(
    scan((s, reducer) => reducer(s), []),
    shareReplay(1)
  )
}

模板

<div> *ngFor="let user of users$ | async">
   <div>{{ user.name }}</div>
   <div><mat-checkbox [ngModel]="user.checked" 
     (ngModelChange)="userService.userChangeSubject.next([user.id, { checked: $event.checked }])"></mat-checkbox></div>
</div>
<div>Checked users: {{ checkedusers$ | async | json }}</div>

几点评论:

  • 您不必直接在服务上就该主题调用 next。为了简洁起见,我在示例中这样做了。如果您喜欢,请随意将它们包装在方法中。
  • 为了简洁起见,再次使用元组是另一个决定。
  • 服务中的每个主题 return 都是一个缩减器,它本身 return 用户数组的一个新副本。这就是为什么当单个用户发生变化时,我会创建一个全新的用户。我本可以直接将值赋给现有数组的元素,但我已经养成了不改变任何变量的习惯。
  • 如果您需要查看 checkedUsers,则映射来自 users$ 可观察到的过滤数组:users$.pipe(map(x => x.filter(y => y.checked))).

类似这样的方法似乎有效:

import { Injectable } from '@angular/core';
import {  merge, of, Subject } from 'rxjs';
import { map, scan } from 'rxjs/operators';

export interface User {
  id: number;
  name: string;
  checked?: boolean;
}

@Injectable({ providedIn: 'root' })
export class UserService {
  readonly userChangeSubject = new Subject<User>();
  userChanged$ = this.userChangeSubject.asObservable();

  allUsers$ = of(userData);

  updateUser(user: User, checked: boolean) {
    const updatedUser = { ...user, checked: checked };
    this.userChangeSubject.next(updatedUser);
  }

  checkedUsers$ = merge(
    this.allUsers$, 
    this.userChanged$
    ).pipe(
    scan((users: User[], user: User) => users.map((u) =>  u.id === user.id ? user : u)),
    map(users => users.filter((u) => u.checked))
  );
}

export const userData: User[] = [
  { id: 1, name: 'Captain Jack', checked: false },
  { id: 2, name: 'Mal Reynolds', checked: false },
  { id: 3, name: 'NaomiNagata', checked: false },
];

这是更新后的 link:https://stackblitz.com/edit/angular7-rxjs-ha6suw

  • 当用户的支票被更改时,Subject 发出一个用户。
  • 需要更改 allUsers$ 以调用您的 http 端点
  • 选中或取消选中复选框时从组件调用 updateUser。它使用适当的检查标志创建一个新的用户对象并将其发送到主题中。
  • checkedUsers$ 将原始用户集与每个已更改的用户合并。然后它使用 scan 来保留用户数组。当发出更新的用户时,它会在数组中找到该用户并替换它。然后它将生成的数组映射为仅显示选中的数组。

更新:根据 OP,添加了“全部选中”和“取消全部选中”功能。

@Injectable({ providedIn: 'root' })
export class UserService {
  // User or null for all users
  // Defined as a tuple with the user and checked flag
  readonly userCheckedSubject = new Subject<[User | null, boolean]>();
  userChecked$ = this.userCheckedSubject.asObservable();

  // This would be an http.get to get the users
  retrievedUsers$ = of(userData);

  allUsers$ = merge(this.retrievedUsers$, this.userChecked$).pipe(
    // Use array destructuring to access the tuple values
    scan((users: User[], [changedUser, checked]: [User | null, boolean]) =>
      this.processUsers(users, changedUser, checked)
    ),
    shareReplay(1)
  );

  checkedUsers$ = this.allUsers$.pipe(
    map((users) => users.filter((u) => u.checked)),
    shareReplay(1)
  );

  updateUser(user: User | null, checked: boolean) {
    this.userCheckedSubject.next([user, checked]);
  }

  processUsers(
    users: User[],
    changedUser: User | null,
    checked: boolean
  ): User[] {
    if (changedUser) {
      // Update the one changed item
      const updatedUser = { ...changedUser, checked: checked };
      return users.map((user) =>
        user.id === updatedUser.id ? updatedUser : user
      );
    } else {
      // Update each item
      return users.map((user) => ({ ...user, checked: checked }));
    }
  }
}

这是更新后的 link:https://stackblitz.com/edit/angular7-rxjs-ahp158