NgRx Tips — Part 4

John Youngers
Youngers Consulting
3 min readFeb 21, 2019

It’s been a while since I’ve written one of these (only a full year), but recently I’ve been doing some front-end work and have more tips to share! Exciting!

Application Specific Store Type

When you inject the store, you’ll likely have these two imports:

import { Store } from '@ngrx/store';
import { ApplicationState } from 'app/app.state';
...constructor(private store: Store<ApplicationState>) {

Since there’s only 1 interface for the state, it seems kind of excessive to need to import both every single time, doesn’t it? We can fix that!

export class MyAppStore extends Store<ApplicationState> {}#app.module
providers: [
{
provide: MyAppStore,
useExisting: Store
}
]
#component
import { MyAppStore } from 'app/app.state';
...constructor(private store: MyAppStore) {

As an added bonus, if NgRx ever bites the dust, you’ll have a layer of abstraction between the new next-best-thing and your code (assuming it follows a similar observable pattern)

TypeScript strict “Object is possibly ‘null’”

In my latest project I decided to hop aboard the pain train and turn the full strict configuration on, and I ran into this scenario quite a bit:

return store.pipe(
select('feature', 'item'),
filter(i => i != null),
map(i => i.toString()) // [ts] Object is possibly 'null'
);

Although we know what we meant, filter rarely helps us out type-wise (note the instanceOf method from an earlier article). To resolve this issue I created a new pipe-able operator:

export function hasValue<T>(): OperatorFunction<T | null | undefined, T> {
return (source: Observable<T | null | undefined>): Observable<T> => {
return source.lift(new FilterOperator(val => val !== undefined && val !== null));
};
}
return store.pipe(
select('feature', 'item'),
hasValue(),
map(i => i.toString()) // hurray!
);

Routing

If you’re using NgRx to maintain the state of your application, avoid using the router to store anything: no resolves, no route subscriptions, etc. Instead, as routes are hit, use guards and canActivate to keep the NgRx state in sync with the current route. To do so, I usually follow this pattern:

  1. Check the current state: are we already in sync (user left and hit the back button maybe) with this route? If not, dispatch an action (that may trigger an effect) to get things in sync.
  2. Return true (or false if a decision is being made) once the state is back in sync.

Here’s an example around authentication:

@Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(private store: MyAppStore) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.store.pipe(
select('core'),
take(1),
switchMap((c: CoreState) => {
if (!c.authenticated && !c.authenticating) {
this.store.dispatch(new AuthenticateAction());
}
return this.store.pipe(
select('core'),
filter(s => !!s && !s.authenticating),
map(s => s.authenticated)
);
})
);
}
}

Here’s an example where we’re just waiting for data to load:

@Injectable()
export class MyItemGuard implements CanActivate {
constructor(private store: MyAppStore) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const myItemId = route.params['MyItemId'] as string;
return this.store.pipe(
select('items', 'itemId'),
take(1),
switchMap(id => {
if (id !== myItemId) {
this.store.dispatch(new MyItemLoadAction(myItemId));
}
return this.store.pipe(
select('items', 'item'),
hasValue(),
map(() => true)
);
})
);
}
}

I think that’s about it for now: short and sweet! Going forward I’ll try and keep better track of these tips to make sure I’m not missing anything.

--

--