NGRX Tips & Tricks
NGRX Tips & Tricks
Adrian FâciuFollow
May 9, 2018
While working with NgRx I’ve found out a series of tips that I
would have loved to know beforehand. Some of them I’ve
discovered looking at different ways people were handling things
and some I’ve found while constantly refactoring the code to make
it cleaner and easier to understand.
Actions
Effects
Reducers
General
Actions
This might feel a bit weird when you start using it, as one would
prefer the less clutter way of factory methods, but using classes
will pay off on the long run:
If you export the type declarations, beside readonly you also have
to write the properties with an extra typeof:
Otherwise the type will be widen to string and that’s not what you
want.
We can also have the type guard in NgRx Effects, where it really
helps:
@Effect()
logError$: Observable<Action> = this.actions$.pipe(
ofType<LogError>(LOG_ERROR),
tap(action => console.log(action.payload))
)
We used ofType to specify the action that this effect will handle
and from that point on we can be sure that the payload object is
what we expect, meaning an object with the string property named
message. This is really useful when you write or refactor the code
because the compiler will show an error each time you make a
mistake.
Also, I always prefer to have the payload an object that has other
properties even if just one. So for example, in the LogError case
one can easily defined it like:
constructor(readonly payload:string ) { }
Inside a larger app one will have for sure actions that have objects
as a payload. Using it like that all the time makes it consistent and
easier to read and understand the intent.
Since all of the actions are in separate files we can simply use
shorter names for them. For constants:
Effects
@Effect()
public errors$: Observable<Action> = this.actions$
.pipe(
ofType(LOG_ERROR),
map(toPayload),
...
)
After the map we can use the payload data without having to refer
to it as action.payload.foo each time we want something from
there.
As a cleaner alternative we can use destructuring to fetch the
payload property:
@Effect()
public errors$: Observable<Action> = this.actions$
.pipe(
ofType(LOG_ERROR),
tap(({ payload }) => console.log(payload)),
...
)
We can specify that we don’t want to do this and just do some side
effect whenever a certain action takes place:
Inside an effect we catch this action, make the request and map
the outcome to a success action:
loadDocuments$ = this.actions$.pipe(
ofType<documentActions.Fetch>(documentActions.FETCH)
switchMap(() =>
this.documentsService
.getDocuments().pipe(
map(docs => new
documentActions.FetchSuccess(docs))
)
)
)
In this way all the requests are isolated from our application and
we have a central place to do each of them. We might have
multiple places from which we can trigger a documents fetch and
all we have to do is send the fetch action.
loadDocuments$ = this.actions$.pipe(
ofType<documentActions.Fetch>(documentActions.FETCH)
switchMap(() =>
this.documentsService
.getDocuments().pipe(
map(docs => new documentActions.FetchSuccess(docs)),
catchError(err => of(new
documentActions.FetchFailed()))
)
)
)
The actions stream is the main one that all our actions are emitted
onto, so when we want to make a request to load the document,
that might fail, we do it inside a switchMap operator where we
catch the error and return a different action.
Reducers
The official sample app exemplifies this in a good way, this is how
a reducer should look:
If you have something that looks more complicated than this there
is place to refactor and make it slightly better.
General
Use selectors
On the store class we have the select method, and there also is a
pipeable select function. We can use both of them to fetch a part
from the state in places where it’s needed.
While you can pass the keys/path to what you want to fetch as a
string argument to these functions I recommend using
the createSelector function provided by NgRx which, as the name
suggests, allows us to create a callback function that knows how to
fetch a part of the state.
we can change the way our state looks behind the scenes
without actually changing the app code, since the selector will
be the same one
we can group together different parts of the state and get what
we need
the selectors use a technique called memoization which
basically means that once we call one with some arguments it
will store that result and whenever we call it again it will return
the cached result without executing the function
Normalize data
We should think of the state like a local, in memory database. One
important thing we should not do is duplicate the same
information all over the place.
{
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324" ]
}
},
"users": {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
"comments": {
"324": { id: "324", "commenter": "2" }
}
}
}
Another benefit of keeping the state like this is that it will allow us,
more easily, to add a caching layer to our application. Only loading
data when we don’t have it, or what we have is outdated.
Don’t use the store all over the place
This is more about ‘dumb components’ than anything else. Most of
our UI components should be small and dumb, meaning they
should not know that there is a state, how we fetch data and so on.
We can make some of them generic, for example the error ones. In
my experience we tend to use the other ones in more places and
need them explicitly, but if this is not the case you can make those
generic as well.
Then when we can use this in Effects where we handle the error
case:
Property initialization
Initialize directly all selector properties when defining them
instead of constructor or other places. One can have code like:
Hopefully you’ve found some good tips about using NgRx above
😃.