The recent trend in state management has rise thanks to the popular library - Redux. I was very interested in integrating a redux solution to angular in my Echoes Player project. I followed few examples that I have found NgRx.js. These are my first steps with integrating it to Echoes.
UPDATED: RC.6, 9/1/2016
In a one liner - Redux implements the concept of state management using Flux design pattern. It does that using pure functions (reducers) that return a new state given a certain event (type of action) and its event data (payload of action).
The benefits of using Redux are:
NgRx.js is a library for state management inspired by Redux. It exposes few objects that implements the core concepts of Redux: Store, Action, Reducer and more.
Lets start adding NgRx to Echoes Player.
First, I created a new directory in ”src/app/core/store” for defining the store. In this directory i’m defining the store, storing the reducers and defining the data model structure of Echoes.
The index.ts is:
import { compose } from "@ngrx/core/compose";
// reducers
import {videos} from './youtube-videos';
// Echoes State
let EchoesStore = {
videos: []
};
export default compose(
)({ videos });
In order to attach this store to the app, i’m importing it in the app.module.ts (i’m using the excellent Angular2Class angular2 starter kit) as such:
// other imports were removed for this example
import { Store, StoreModule } from '@ngrx/store';
import { store } from './core/store';
import { App } from './app.component';
/**
* `AppModule` is the main entry point into Angular2's bootstraping process
*/
@NgModule({
bootstrap: [ App ],
declarations: [
// others removed for this post
App,
YoutubeVideos,
],
imports: [
// others removed for this post
StoreModule.provideStore(store)
],
providers: [
// others removed for this post
actions
]
})
export class AppModule {}
The first reducer that I have created handles the videos search results. Currently, it handles 3 actions: add, remove and reset.
Notice how the ”videos” function is a pure function: it gets 2 arguments and is expected to return a value.
import { ActionReducer, Action } from '@ngrx/store';
export const ADD = 'ADD';
export const REMOVE = 'REMOVE';
export const RESET = 'RESET';
export interface EchoesVideos extends Array<GoogleApiYouTubeSearchResource>{};
export const videos: ActionReducer<GoogleApiYouTubeSearchResource[]> = (state: EchoesVideos = [], action: Action) => {
switch (action.type) {
case ADD:
return addVideos(state, action.payload);
case REMOVE:
return state;
case RESET:
return [];
default:
return state;
}
}
export function addVideos(state: EchoesVideos, videos: GoogleApiYouTubeSearchResource[]) {
return state.concat(videos);
}
The main (and currently only) action that is implemented here is the ”add” action with addVideos function. The ”addVideos” function simply returns a new array that includes a copy of the source array concatenated with a new videos array.
In order to use the ngrx store in the ”youtube-videos.ts” component, I need to import the Store - which is a ”singletone” object, and inject it.
// youtube-videos.ts
import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core';
import { Store } from '@ngrx/store';
import { EchoesState } from '../core/store';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'youtube-videos',
template: require('./youtube-videos.html'),
changeDetection: ChangeDetectionStrategy.OnPush
})
export class YoutubeVideos {
videos$: Observable<EchoesVideos>;
constructor(private youtubeSearch: YoutubeSearch, public store: Store<any>) {
this.videos$ = store.select(state => state.videos);
}
ngOnInit(){
this.search('')
}
//....more code
}
I also imported the ”ChangeDetectionStrategy”, so I can instruct angular to evaluate changes once.
In order to use the result of the youtube search action, I attach the store’s reducer result to the ”videos” property in this class. Since this operation is async, the template of youtube-videos component has been defined with a pipe of async:
### Using Store in Angular2 Service
The real action of dispatching action to the store comes from the youtube.search service.
I first import the Store and the actions needed for new states:
```typescript
import { Store } from '@ngrx/store';
import { ADD } from '../store/youtube-videos';
Similarly to the youtube-videos component, the ”Store” is injected to this service constructor and attached to the ”this.store” context.
In order to update the store state, once the response is ready, the ”ADD” event is dispatched with the expected payload:
search(query: string, dontReset: Boolean){
const isNewSearch = query && query !== this._config.get('q');
const shouldBeReset = !dontReset;
if (shouldBeReset || isNewSearch) {
this._config.set('pageToken', '');
}
if (query && query.length) {
this._config.set('q', query);
}
return this.http.get(this.url, { search: this._config })
// .map((res: Response) => res.json())
.toPromise()
.then((response) => response.json())
.then((response) => {
let itemsAmount = this.items.length;
this.nextPageToken = response.nextPageToken;
this.store.dispatch({ type: ADD, payload: [ ...response.items ] })
return response;
});
}
This closes a circle and invokes the ”videos” reducer mentioned above, which afterwards updates the youtube-videos components, which in turn, displays the current state of the ”store.videos” property.
This is merely just the beginning of integrating NgRx to Echoes. Right after the start, I noticed the benefits of using it. There are more decisions to take in this project using ngrx as well as adding more reducers.
As always, Echoes is an open source project. The source code is available on github.
Thanks for reading :).