Jim Lynch Codes
  • Blog
  • About Jim

Writings about one coder's stories & experiences.

How To Create An Ngrx @Effect That Doesn't Return An Action

12/31/2016

0 Comments

 
Yep, another blog post about Angular 2 and Ngrx. In this post we'll look at some ways to create @Effects that don't return an action to the reducer and when you might want to do this.

The Classic @Effect Model

Let's take a look at a relatively simple @Effect:
@Effect() updates$ = this.actions$
    .ofType('START_TIMER')
    .switchMap( () =>
      Observable.timer(2000)
        .switchMap(() => Observable.of({ type: "TIMER_DONE" }))
    );
In the code above I'm creating an @Effect named update$. We're listening for actions being dispatched from other places in our app. Normally we won't handle the "START_TIMER" type action in our reducer. It will just "pass through" the reducer and be handled here, as an @Effect. If the action coming in has a type of "START_TIMER" then this effect will go to work! All it's doing though is setting a timer delay for 2 seconds. Then when it's finished it returns an observable to an action of type "TIMER_DONE". Remember, Ngrx takes care of firing off this new action in the background so all you have to do is return an observable with an object that contains the properties of an ngrx Action (ie. type and optionally payload). You can then handle the "TIMER_DONE" type action in your reducer. This normally makes for a great separation of concerns; the effects only have to care about calling out for data and the reducers only need to care about updating the state. @Effects are really for doing any kind of asynchronous action, anything that is a "side effect" (hence the name "ngrx/effects" and name for the @Effect decorator). However, sometimes you might just want to trigger some side effect without really needing to send a response action back to your reducer, and that's â€‹what this post is about. :)

A Use Case For Not Returning An Action

I'm using "anonymous authentication" with Firebase to sign in a user automatically as a guest upon landing on the page. In my component I'm listening for the onAuthStateChanged event on the firebase auth object and handling it as a promise:
firebase.auth().onAuthStateChanged( (user) => {
  if (user) {
    this.store.dispatch(type: "ANONYMOUS_AUTH_SUCCESS", payload: {uid: user.uid, isAnonymous:user.isAnonymous}));
  }
  else {
    this.store.dispatch({type:"BEGIN_ANONYMOUS_AUTH"});
  }
})
Let's let's think about these two cases. If the user lands on the page and firebase already has the auth information saved in the session (which is usually the case for returning users) then it will run the code inside the "if" block which dispatches a new action, "ANONYMOUS_AUTH_SUCCESS". We'll pass along the user's name and isAnonymous property so that they can be saved in our ngrx/store (note: we could also use the ngrx-store-localstorage library to save our store after the session ends (ie. if the user closes the browse, opens it back up, and goes back to the page). Anyway, if our user is landing on the page for the first time or has totally cleared their firebase browser history then the else block will run, triggering an action of type "BEGIN_ANONYMOUS_AUTH" to be dispatched. Since this action requires an asynchronous call to firebase to authenticate, the action bypasses our reducer and is handled as an effect. Here's how we might implement it using our standard pattern above: 
@Effect() anonymousAuthr$ = this.actions$
    .ofType('BEGIN_ANONYMOUS_AUTH')
    .switchMap(() => {
      return Observable.fromPromise(<Promise<any>>firebase.auth().signInAnonymously())
        .switchMap((result) => {
          return Observable.of({
            type: "ANONYMOUS_AUTH_SUCCESS", payload: {uid: result.uid, isAnonymous: result.isAnonymous}
          })
        })
    });
The problem with returning the "ANONYMOUS_AUTH_SUCCESS" action here is that the firebase call itself will come back and trigger the initial onAuthStateChanged event to be fired again in our component once the user is successfully authenticated. Thus, if we return the "ANONYMOUS_AUTH_SUCCESS" in our effect then it will lead to the action being dispatched twice (which is not the end of the world, but it could make the code more confusing and harder to maintain. It's like an unnoticable bug, and it's hard to spot unnoticable bugs!). It would be much cleaner if we could just call to firebase and then chop off the effect right there in the succes handler. Let's see some ways of doing that. 

Returning an Unused Action

The thing to remember is that our function has to return an Observable; it can't just return nothing. One simple solution would be to return an action that is dedicated to being the "unhandled" action. In this example below I'm returning an Observable to an action with no payload and with a type, "NO_ACTION":
@Effect() anonymousAuth$ = this.actions$
    .ofType('BEGIN_ANONYMOUS_AUTH')
    .switchMap( () => {
      return Observable.fromPromise(<Promise<any>>firebase.auth().signInAnonymously())
        .switchMap( (user) => {
          return Observable.of({type:"NO_ACTION"});
        })
    })

Using "{dispatch: false}"

In the @Effect decorator function we can also pass in an argument to tell the effect, "Hey! Don't actually dispatch the action observable that I'm returning here!". Remember, you still have to return some observable. It could be any Observable, an Observable of an empty object, or even just null.
@Effect({dispatch: false}) anonymousAuth$ = this.actions$
    .ofType('BEGIN_ANONYMOUS_AUTH')
    .switchMap( () => {
      console.log('yep, doing the anonymous auth')
      return Observable.fromPromise(<Promise<any>>firebase.auth().signInAnonymously())
        .switchMap( (result) => {
          
          // Any of these will work
          
           return Observable.of({})
           return (<ObservableInput<any>>{});
           return null;
        });
    })

Use It Wisely

Remember that most of your @Effects should follow the classic "get an action in, do some async stuff, return an action back with some new data" pattern, and these should be the bulk of your effects. However, for special situations like the one described here it's nice to have the flexibility to create @Effects for doing side effects but then don't return an action. It's great that ngrx/effects allows us to do this, and hopefully next time you want to do it you won't be wondering how! ;)
0 Comments

Your comment will be posted after it is approved.


Leave a Reply.

    ​Author

    Picture
    The posts on this site are written and maintained by Jim Lynch. About Jim...
    Picture
    Follow @JimLynchCodes
    Follow @JimLynchCodes

    Categories

    All
    Actionscript 3
    Angular
    AngularJS
    Automated Testing
    AWS Lambda
    Behavior Driven Development
    Blockchain
    Blogging
    Business Building
    C#
    C / C++
    ClojureScript / Clojure
    Coding
    Community Service
    CS Philosophy
    Css / Scss
    Dev Ops
    Firebase
    Fitness
    Flash
    Front End
    Functional Programming
    Git
    Go Lang
    Haskell
    Illustrations
    Investing
    Java
    Javascript
    Lean
    Life
    Linux
    Logic Pro
    Music
    Node.js
    Planning
    Productivity
    Professionalism
    Python
    React
    Redux / Ngrx
    Refactoring
    Reusable Components
    Rust
    Security
    Serverless
    Shell Scripting
    Swift
    Test Driven Development
    Things
    TypeScript
    Useful Sites
    Useful Tools
    Video
    Website Development
    WebStorm
    Writing

    Archives

    March 2023
    August 2021
    February 2021
    January 2021
    October 2020
    September 2020
    May 2020
    April 2020
    February 2020
    January 2020
    December 2019
    October 2019
    September 2019
    August 2019
    July 2019
    June 2019
    May 2019
    April 2019
    March 2019
    February 2019
    January 2019
    December 2018
    November 2018
    October 2018
    September 2018
    August 2018
    June 2018
    May 2018
    April 2018
    March 2018
    February 2018
    January 2018
    December 2017
    November 2017
    October 2017
    September 2017
    August 2017
    July 2017
    May 2017
    April 2017
    March 2017
    February 2017
    January 2017
    December 2016
    November 2016
    October 2016
    September 2016
    August 2016
    July 2016
    June 2016
    May 2016
    April 2016
    March 2016
    February 2016
    January 2016
    December 2015
    November 2015
    October 2015

    RSS Feed

  • Blog
  • About Jim
JimLynchCodes © 2023