如何从封装在服务中的流中获取已检查的用户?
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
有一个服务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