Reputation: 6160
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
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
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