Ryan Langton
Ryan Langton

Reputation: 6160

Combine initial stream with modify streams with RxJS

I'm trying to think functional reactive and set up a list of elements on a page as a stream. This is using Angular2 but the problem should be similar to any stream-based architecture. So, I have two streams currently, the initial stream (http call to get a list of users from github) and a remove user stream (occurs when the remove user button is clicked). I believe the marble diagram would look like the following:

|[user1,user2,user3]|                       <--- http initial stream
|---------------------x----------x-----...  <--- x denotes user removed

How do I combine these streams to get this to work? I'm also thinking later of having more streams for sorting and ordering. Am I going about this the right way? Here's the code (note this code is incomplete, currently the removeUser$ is not interactive with the user$ which it should):

export class UserGridComponent implements OnInit { 
    public users$: Observable<any>;
    public removeUser$: Subject;

    constructor(private _githubUserService: GithubUserService) { }

    ngOnInit() { 
       this.removeUser$ = new Subject()
           .subscribe((user) => { console.log('next ' + JSON.stringify(user)});
       this.users$ = this._githubUserService.getUsers()
           .map((res) => res.json());
    }
}

Here is the Plunker

Currently I am only logging to console that the remove button is clicked and passes the user.

Here is the html template which shows that I subscribe to the user$ by using the async pipe (Angular2):

<md-list>
  <h1 md-header>GitHub Users</h1>
  <md-list-item *ngFor="let user of users$ | async">
    <a href="https://github.com/{{user.login}}" target="_new">
      <img md-list-avatar [src]="user.avatar_url">
    </a>
    <h4>{{user.login}}</h4>
    <button md-icon-button (click)="removeUser$.next(user)">
      <md-icon>cancel</md-icon>
    </button>
  </md-list-item>
</md-list>

Upvotes: 4

Views: 859

Answers (2)

Davy
Davy

Reputation: 6441

Take a look at this stackblitz: it shows how you can use scan to mutate a single value (which can be anything ...) by events emitted from one or multiple sources. In the example, all incoming actions come from on modify$ stream, but you might as well merge together an add$ and a remove$ stream.

https://stackblitz.com/edit/angular-evc48h

But ... i would not recommend doing this because this is -exactly- what a state store does for you.

So be sure to check out the well-known ngrx: https://github.com/ngrx/platform

Or the lesser known but cleaner ngxs: https://github.com/ngxs/store

I often hear people claim that using a state store introduces unnecessary complexity, and this might be true for small and simple screens. The more complex your screen gets however, the more a state store will help you keep it elegant and clean.

Upvotes: 0

Can Nguyen
Can Nguyen

Reputation: 1470

ngOnInit() { 
   this.users$ = new BehaviorSubject([]); // init with empty user array
   this._githubUserService.getUsers()
       .map((res) => res.json())
       .subscribe(this.users$);

   this.removeUser$ = new Subject();
   this.removeUser$.subscribe((user) => { 
       this.users$.take(1)  // get current users, as users$ is a BehaviorSubject
           .map(users => users.filter(u => u != user))   // remove `user`
           .subscribe(this.users$); // update users$ stream
   });
}

Hope my comments can serve as explanations. BehaviorSubject cache last emitted value, so this.users$.take(1) is synchronous. I find myself using a BehaviorSubject as source for angular2 ' | async' pipe very often.

EDIT: Same idea but a little shorter for the removeUser$ part:

.....
   this.removeUser$.withLatestFrom(user$, 
       (toRemove, users) => users.filter(u => u != toRemove)   // remove `toRemove`
   ).subscribe(this.users$); // update users$ stream

Upvotes: 1

Related Questions