Home > Blockchain >  How to get checked users from stream incapsulated in service?
How to get checked users from stream incapsulated in service?

Time:11-10

There is a service UsersService contains user logic:

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();
   }
}

I get data from service inside component:

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

Template is:

<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>

How to get all checked users using rxjs approach {{ checkedusers | json }} from stream after updated one checkbox or all checkboxes and incapsulate it into service?

CodePudding user response:

Use the scan operator to combine all of the sources that could modify the users list into one stream. Then in the component, instead of modifying user objects directly, send the changes to one of the subjects that is being used to modify the list:

Service

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)
  )
}

Template

<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 | json }}</div>

A few comments:

  • You don't have to call next on the subject directly on the service. I did that for brevity in the example. Feel free to wrap them in methods if that's your thing.
  • The use of the tuple was again another decision made for brevity's sake.

CodePudding user response:

Something like this seemed to work:

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 },
];

Here is the updated link: https://stackblitz.com/edit/angular7-rxjs-ha6suw

  • The Subject emits a user when the user's check is changed.
  • The allUsers$ will need to be changed to call your http endpoint
  • The updateUser is called from the component when a checkbox is checked or unchecked. It creates a new user object with the appropriate check flag and emits it into the Subject.
  • The checkedUsers$ merges the original set of users with each emitted changed user. It then uses scan to retain the array of users. When an updated user is emitted, it locates that user in the array and replaces it. It then maps the resulting array to only display those that are checked.
  • Related