DEV Community

paulmojicatech
paulmojicatech

Posted on • Updated on

An Introduction to RxJs and NgRx

What is RxJs?

RxJs is the javascript representation of the ReactiveX programming pattern which deals with code execution in an asynchronous environment. It uses the pub / sub model where there is a publisher (something that broadcasts data) and a subscriber (something that is interested in that data).

RxJs is written in a declarative way instead of in an imperative way. This is done by using RxJs's operators that listen for data to be published, then can transform that data and pass it to the next operator to do more transformations. Take a look at the code below:

Imperative Way

@Component({
    template: `
        <div>
            <button (click)="getJokes()">Get Jokes</button>
        </div>
        <ul>
            <li *ngFor="let joke of allJokes">
                <span>{{joke.author}}</span>
                <div>{{joke.details}}</div>
            </li>
        </ul>
    `
})

constructor (private _jokesHttpSvc: JokesHttpService, private _authorsHttpSvc: AuthorsHttpService){}

allJokes: { author: string; details: string;}[] = [];

getJokes(): void {
    this._jokesHttpSvc.getJokes.subscribe(jokes => {
        this._authorsHttpSvc.getAuthors().subscribe(authors => {
            jokes.forEach(joke => {
                authors.forEach(author => {
                    if (author.id === joke.authorId) {
                        this.allJokes.push({
                            author: author.name,
                            details: joke.details
                        });
                    }
                })
            })
        })
    })
}

Enter fullscreen mode Exit fullscreen mode

Declarative Way

@Component({
    template: `
        <div>
            <button #getJokesBtn>Get Jokes</button>
        </div>
        <ul>
            <li *ngFor="let joke of allJokes$ | async">
                <span>{{joke.author}}</span>
                <div>{{joke.details}}</div>
            </li>
        </ul>
    `
})

constructor (private _jokesHttpSvc: JokesHttpService, private _authorsHttpSvc: AuthorsHttpService){}

@ViewChild('getJokesBtn')
getJokesBtn: ElementRef<HTMLButtonElement>;

allJokes$: Observable<{ author: string; details: string;}>[];

ngAfterViewInit(): void {
    const jokes$ = this._jokesHttpSvc.getJokes();
    const authors$ = this._authorsHttpSvc.getAuthors();
    this.allJokes$ = fromEvent(this.getJokesBtn.nativeElement, 'click').pipe(
        switchMap(() => forkJoin([jokes$, authors$]).pipe(
            map(joinedStream => {
                const [jokes, authors] = joinedStream;
                return jokes.map(joke => {
                    const jokeAuthor = authors.find(author => author.id === joke.authorId);
                    return {author: jokeAuthor.name, details: joke.details};
                });
            })
        ))
    )
}

Enter fullscreen mode Exit fullscreen mode

Things to Note:

  • In the imperative way, we bind the button to a click event that calls a method in the component. In the declarative way, we create the listener by creating an observable for (fromEvent operator) for when the button is clicked.
  • In the imperative way, we mutate the component by updating the allJokes property every time the Get Jokes button is clicked. In the declarative way, we create an observable allJokes$ that listens for the button clicks then executes code to transform the responses to an object that we want to consume. This makes the component pure (no side effects) as the allJokes$ property is never mutated.
  • We use Angular's async pipe to make the allJokes$ observable "hot". Observables are by default "cold" meaning they will not execute until something is listening (or subscribing) to it. This is different from a promise where a promise executes immediately but only executes once. An observable executes until the subscription or observer completes. Async pipe is Angular's way to subscribe from the HTML template, which will auto complete when the component is destroyed.

NgRX and Why We Need It

In a complex application, pieces of data must be used in different parts of the application. The way that data is shared across the application is stat management. We must think about how / when we should get that data as well as how we should share the data. We also want to think about how we write our code in such a way that is maintainable and understandable to the team.

Enter NgRx. NgRx is the Angular implementation of the Redux pattern. It heavily utilizes RxJs (Ng = Angular, Rx = RxJs) for this implementation. The redux pattern centers around state being an immutable object. The thought is that we should be able to trace back any events (actions) that occur in the system to get a true representation of the application in a moment of time.

There are 4 main pieces to NgRx:

  • Actions
  • Effects
  • Reducers
  • Selectors

Actions

Actions are the events that are flowing through the application. Dispatching an action means publishing an event that may contain data. We then have listeners (or subscribers) to these events.

Effects

Effects are one of the subscribers / observers to the actions that are published by the system. Actions that are going to mutate the state of the application should be handled by effects. We specifically call out the effect will mutate the application and NOT the state store itself.

An effect takes in an observable (the Actions observable from the `@ngrx/effects` package) and returns another obserable. The observable returned is in the form of an action, which our reducer listens (or subscribes) to. Note: You can have an effect that does not return an action but you must specify `{dispatch: false}`, otherwise your browser will get very mad at you... trust me. The dispatch: false tells our application that we are not dispatching an action from the effect. This is useful when you want an effect to open / dismiss a modal or spinner.

Reducers

Reducers are another subscriber to actions. The difference between an effect and a reducer is that it updates the state store object by creating a new state store object when an action is dispatched (or published) with new data. An important piece to a reducer is that it does NOT mutate the state store. Instead, it creates a new state store with the updated data from the action. This allows for us to have an audit trail of events in the application to get a historical representation of the state of the application at any given point.

Selectors

Selectors are subscribers to the state store that emit (or publish) an event that other parts of the application can subscribe to. Selectors can take parts of the entire store and expose those slices of state. The selectors are memoized to help with application performance.

Conclusion

RxJs in conjunction with NgRx is a powerful way to maintain state in an application. It delivers a declarative and functional (non mutating) implementation which makes debugging the state of the application much easier. It also resembles the pub / sub and event driven design patterns that seasoned developers may be familiar with. I have another article (Domain Driven Design with NgRx) that goes into NgRx a little more.

I hope you found this article useful. If you want to hear more about my thoughts on software, follow me at

@paulmojicatech on Twitter.

Thanks and happy coding!

Oldest comments (0)