The Ngrx projects adds functional approach to various interesting architectural implementations. I wrote about my experience with storing data using ngrx/store and testing this approach in Angular (+2) based app. Since then, I’ve had the chance of exploring this land of ngrx further more. I’ve found ngrx/effects to be an elegant and quite functional solution for expressing a series of actions which depend on each other. In this post, I explain a use case for using ngrx/effects in my open source app, Echoes Player, and show the benefits that I found, after using it.
[UPDATED: 12/2/16, with ngrx/effects 2) This title is a little bit overwhelmed - however, after seeing the “light” from another perspective - I allow myself to say that I prefer to use promises as the last implementation approach nowadays when using ngrx/store.
Usually, we’re using promises in order to fetch data (async), save new data or generally, update a state with a REST api - CRUD operations. we’ve come a long way with promises - promises features few benefits:
With these benefits, or this “power”, there, “comes great responsibility”:
To sum up, promises are good and perhaps better than simple ajax based callbacks - however - we can make it better.
Echoes Player is a media player which is based on youtube api (production app in written with AngularJS). Its layout is a common layout of a dashboard app -
The user can click on “Queue” in any media card in order to add the media the now playlist.
I’ve started porting Echoes Player to Angular (+2) in order to experiment with Angular (+2) and various interesting solutions - among - ngrx/store and ngrx/effects. The above screenshot is taken from a talk I gave recently (AngularJs-IL , July 2016) about Angular (+2) and ngrx/store - I also took the chance to introduce the concept of ngrx/effects.
The component that manages the right pane is ”youtube-videos.component.ts”. handling the ”Queue” event was implemented with this code in the ”queueSelectedVideo” function:
// "imports" omitted to focus on the relevant code for this example
@Component({
selector: 'youtube-videos.youtube-videos'
})
export class YoutubeVideos implements OnInit {
videos$: Observable<EchoesVideos>;
playerSearch$: Observable<PlayerSearch>;
constructor(
private youtubeSearch: YoutubeSearch,
private nowPlaylistService: NowPlaylistService,
private store: Store<EchoesState>,
public youtubePlayer: YoutubePlayerService
) {
this.videos$ = store.select(state => state.videos);
this.playerSearch$ = store.select(state => state.search)
}
playSelectedVideo (media: GoogleApiYouTubeSearchResource) {
this.youtubePlayer.playVideo(media);
this.queueSelectedVideo(media)
.then(videoResource => this.nowPlaylistService.updateIndexByMedia(videoResource));
}
queueSelectedVideo (media: GoogleApiYouTubeSearchResource) {
return this.nowPlaylistService.queueVideo(media.id.videoId);
}
}
The ”queueSelectedVideo” function calls the queueVideo function on the nowPlaylistService. In this implementation, the ”playSelectedVideo” function also uses the queueSelectedVideo and it’s easy to understand that this function return a promise (spot the “then”).
The reason for using a promise here is - in order to display more data on the selected video that is “destined” to queue, I had to make an api call to another youtube api and only then, add this media to the now playlist sidebar.
”queueVideo” function, simply, dispatch an action once the promise has been resolved:
// now-playlist.service.ts
// imports omitted
@Injectable()
export class NowPlaylistService {
public playlist$: Observable<YoutubeMediaPlaylist>;
constructor(public store: Store<any>,
private youtubeVideosInfo: YoutubeVideosInfo
) {
this.playlist$ = this.store.select(state => state.nowPlaylist);
}
queueVideo (mediaId: string) {
return this.youtubeVideosInfo.api.list(mediaId)
.then(response => {
this.store.dispatch({ type: QUEUE, payload: response.items[0] });
return response.items[0];
});
}
}
This code is simple and works great. However, there might be few challenges that will be hard to implement with this approach:
Ngrx/Effects comes to play in situations. But first, lets understand what is an Effect.
Effects relates to the term ”side effect” - a function has a side effect if it ”modifies some state or has an observable interaction” (wikipedia). With our case, we can say that the queue video action eventually modifies the state of now-playlist, has a side effect of initiating an api call to a service and then, upon response (success or error) and will initiate another action.
See? there is a chain reaction which originated in one action - this chain reaction should always occur in Echoes Player app whenever the ”Queue” action is raised.
The 2nd fact to notice here is that ngrx/store is based on observables. Ngrx/Effects is based on theses observables and operates by subscribing to its changes. The discovery and understanding on how to use Ngrx/Effects, led me to refactor the code for now playlist and its dependent services, while hardening the logics more and separating it to a higher degree from services.
Lets see how ngrx solves the above challenges while including the benefits of promises.
First, now I created a new class to be used as an action creator - functions which will return an Action object. i.e, the ”now-playlist.actions.ts” is:
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
@Injectable()
export class NowPlaylistActions {
static QUEUE_LOAD_VIDEO = 'QUEUE_LOAD_VIDEO';
queueLoadVideo(media): Action {
return {
type: NowPlaylistActions.QUEUE_LOAD_VIDEO,
payload: media
}
}
// other functions omitted for this post
}
the ”youtube-videos.component.ts” has been revamped to use these action creators functions. Now, this component just dispatch actions with ”playSelectedVideo” and ”queueSelectedVideo”. There are no logics or ajax resolving here:
// imports omitted for this post
@Component({
selector: 'youtube-videos.youtube-videos'
})
export class YoutubeVideos implements OnInit {
videos$: Observable<EchoesVideos>;
playerSearch$: Observable<PlayerSearch>;
constructor(
private youtubeSearch: YoutubeSearch,
private nowPlaylistService: NowPlaylistService,
private store: Store<EchoesState>,
private nowPlaylistActions: NowPlaylistActions,
private playerActions: PlayerActions,
public youtubePlayer: YoutubePlayerService
) {
this.videos$ = store.select(state => state.videos);
this.playerSearch$ = store.select(state => state.search)
}
playSelectedVideo (media: GoogleApiYouTubeSearchResource) {
this.store.dispatch(this.playerActions.loadAndPlay(media));
// This dispatch an action as well)
this.nowPlaylistService.updateIndexByMedia(media.id.videoId);
this.store.dispatch(this.nowPlaylistActions.queueLoadVideo(media))
}
queueSelectedVideo (media: GoogleApiYouTubeSearchResource) {
this.store.dispatch(this.nowPlaylistActions.queueLoadVideo(media));
}
}
Lets focus on the new flow of ”queueSelectedVideo“.
Along side the refactored code, I created a new directory for effects. This is the ”now-playlist.effects.ts“:
// imports omitted for this post
@Injectable()
export class NowPlaylistEffects {
constructor(
private store$: StateUpdates<EchoesState>,
private nowPlaylistActions: NowPlaylistActions,
private nowPlaylistService: NowPlaylistService,
private youtubeVideosInfo: YoutubeVideosInfo
){}
@Effect() queueVideoReady$ = this.store$
.ofType(NowPlaylistActions.QUEUE_LOAD_VIDEO)
.map<GoogleApiYouTubeSearchResource>(action => action.payload)
.switchMap(media => this.youtubeVideosInfo.fetchVideoData(media.id.videoId)
.map(media => this.nowPlaylistActions.queueVideo(media))
.catch(() => Observable.of(this.nowPlaylistActions.queueFailed(media)))
);
}
There a new decorator - ”@Effect()” - which is used to what I call - a side effect ”story”. In my opinion, the code is almost a simple story from which I can understand a chain of reactions:
Lets see the benefits this implementation allows - similarly to the benefits of promises:
Moreover, now, I can update the state of the app and react to several states with this approach - requesting to queue a video (imagine a ”loading more video data…” spinner) and adding the video to the queue (”added to the queue!” message). On the contrary, if the video has been deleted and an error will be returned by the api, a proper message can be displayed to the user (not implemented for now - but relatively very easy to add).
I find ngrx/effects a very interesting and neat approach to group logics which involves a chain reactions of several actions. It promotes readability of the code, think “twice” about your app’s design - to both logics and visual.
Another useful outcome I experienced - writing with ngrx/Effects in mind, promotes creating smaller SMART components (container components) - in which, these components communicates with the store through action creators - reducing the components “logics” to bare minimum calls of actions.
I intend to explore ngrx/effects further - testing effects, using effects dynamically as well as exploring alternatives.
You can explore Echoes Player with Angular (+2), Echoes Player with Angular (+2) and off course, user the actual production app at - http://echoesplayer.netlify.app
book my recent session to your teams for FREE
Register for a full day workshop on Angular (+2) & Ngrx/Store: