Early thoughts on Angular with Redux (NgRx)

Matt Eland - Sep 1 '19 - - Dev Community

I've built a number of single page applications in Angular 2, 4, 6, and now 8 (for some reason I tend to like the even-numbered versions).

With 8, I've looked into NgRx and it's a significantly different experience. I want to share some early thoughts as I'm early on in this journey. This will be more about my reactions and less about the code, though I intend to follow-up on this later with a proper tutorial.

Angular Heroics

My prior experience in Angular revolved around:

  • Breaking a page down into individual components needed for the UI
  • Introducing a service for each major piece of application data needed as well as specialized services for things like logging, web communications, showing notifications, etc.
  • Wiring things together with Routes and Child Routes
  • Using Resolvers to pull data out of Services and provide them to Components based on the Route chosen
  • Using Guards to prevent invalid use of Routes

Most Angular development involved adding Components or Services, then managing subscriptions in ngOnInit and ngOnDestroy and binding in the template. Dependency Injection made the world go round with easy access to services from components and things stayed simple and usable.

State strikes back

But things got complicated. In applications with client and server state, the user would make a change and you would then call out to the server to tell the server about the local change. If the server rejected the change, you had to update the UI. Meanwhile, the user could be performing other actions which resulted in other calls. Additionally, some subsequent calls could complete before their earlier calls have returned to the UI.

All of this brews a storm of opportunities for confusing state and communications-related bugs. I've actually resorted to recording video of my network tab mixed with the application UI in order to watch the state of the application and what operations caused the UI to get out of sorts.

NgRx to the Rescue?

NgRx, or Reactive Libraries for Angular, brings the Redux pattern of state management to Angular. The Redux pattern is designed as a state-oriented cycle where the UI dispatches actions that a reducer uses to update state, which then is dispatched back to the UI. The key factors here are that state is managed in a centralized spot, and only a handful of reducer actions can modify state, so if a bug occurs in state management, the culprit is easily identifiable.

Beyond that, the dev tools for Redux are fantastic.

Redux Dev Tools

The tools let you look at the history of your application state over time and even jump between different state versions and watch your UI update. This makes the complexities of debugging asynchronous state changes significantly easier.

The downside of this is that instead of dealing with services and methods for the majority of your actions, you have to think in a much more reactive manner, considering event streams, selectors, etc.

For example, to respond to a user event, instead of this:

  public onBeginShiftClick(): void {
    this.shiftService.beginShift();
  }
Enter fullscreen mode Exit fullscreen mode

you would do this:

  public onBeginShiftClick(): void {
    this.state.dispatch(beginShiftAction());
  }
Enter fullscreen mode Exit fullscreen mode

This gets dispatched to a reducer which looks like this:

const gameReducer = createReducer(GameSimulator.buildDefaultState(),
  on(beginShiftAction, state => GameSimulator.simulate(state)),
  // ....
);

Enter fullscreen mode Exit fullscreen mode

Not too hard, I admit.

Where it gets very different is how you get data out of the redux store. Instead of doing something like:

@Component({
  selector: 'ssit-crew-page',
  templateUrl: './crew-page.component.html',
  styleUrls: ['./crew-page.component.styl']
})
export class CrewPageComponent implements OnInit {

  public crew: CrewMember[];

  constructor(private crewService: CrewService) {
  }

  ngOnInit() {
    this.crew = this.crewService.crew;
  }

}
Enter fullscreen mode Exit fullscreen mode

You would instead do:

@Component({
  selector: 'ssit-crew-page',
  templateUrl: './crew-page.component.html',
  styleUrls: ['./crew-page.component.styl']
})
export class CrewPageComponent implements OnInit {

  public crew$: Observable<CrewMember[]>;

  constructor(private store: GameStateStore) {
  }

  ngOnInit() {
    this.crew$ = this.store.select(this.store.getCrewMembers);
  }

}
Enter fullscreen mode Exit fullscreen mode

Because you're now working with an observable stream, you also have to remember to use the async pipe (e.g. crew | async) in your template so that the event is auto-subscribed / unsubscribed.

Additionally, you need to implement the selector in the state store:

const gameSelector = createFeatureSelector<GameState>('game');

export class GameStateStore extends Store<GameState> {
  public readonly getCrewMembers = createSelector(gameSelector, (state: GameState) => state.crew);
  // ...
}

Enter fullscreen mode Exit fullscreen mode

Closing Thoughts

Overall, I'm very early on in my NgRx journey. There are definite advantages and disadvantages so it's not for every project.

Pros

  • Creates a very clear audit trail of state changes
  • State changes occur in isolated locations
  • No need to subscribe / unsubscribe from events from services (technically, these could be handled with observable streams and pipes, but historically I've subscribed so I'm bundling the new behavior in with NgRx)

Cons

  • More complex than working with individual services
  • Selector syntax can be hard to interpret
  • Async pipes can be cumbersome and muddle up your templates
  • My development speed is much lower, but a lot of that is due to constant learning a different flavor of Angular

I'm going to keep working on NgRx and Angular with my learning project. I'll keep you posted.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player