DEV Community

Don
Don

Posted on

A better way to connect to NgRx store

As shown in the NgRx documentation example, in order to access the NgRx state, we have to create an observable and assign a selector to it. Due to the nature of observable, async pipe is required to apply to the observable in the HTML template. This is quite cumbersome in general.

export class MyCounterComponent {

  public count$: Observable<number>;

  constructor(private store: Store<{ count: number }>) {
    this.count$ = store.select('count');
  }
}
Enter fullscreen mode Exit fullscreen mode
<div>Current Count: {{ count$ | async }}</div>
Enter fullscreen mode Exit fullscreen mode

In a bid to turning the observable into regular variable. We can assign the state to a variable in the subscription.

export class MyCounterComponent {

  public count: number;
  public count$: Observable<number>;

  private countSubscription: Subscription;

  constructor(private store: Store<{ count: number }>) {
    this.count$ = store.select('count');
    this.countSubscription = this.count$.subscribe(count => {
      this.count = count;
    })
  }

  public ngOnDestroy() {
    this.countSubscription.unsubscribe();
  }
}
Enter fullscreen mode Exit fullscreen mode

However, the above code doesn't look very nice. Once the component has a few more selectors, it could easily become a mess.

Can we come up with an approach so that we can connect the NgRx state with ease? Yes, we can!

We create a AbstractConnectableComponent to automate the observable subscription steps. This improved approach does not need to create the observable properties. The connectable component iterates through the connected properties and assigns the selected NGRX state to them. It is easy to unsubscribe the observables too. Once the observable destroy$ emits in ngOnDestroy() lifecycle hook, the subscriptions will be destroyed. Plus, since these connected public properties are just regular variables, we don't need the async pipe in the HTML template any more. All in all, this is a clever approach to connect NgRx store just with a little bit abstraction.

export class MyCounterComponent extends AbstractConnectableComponent  {

  public count: number;

  constructor(private store: Store<{ count: number }>) {
    this.connect<MyCounterComponent>({
      count: this.store.select('count'),
    })
  }

}
Enter fullscreen mode Exit fullscreen mode
 @Component({ template: '' })
export class AbstractConnectableComponent implements OnDestroy {

  protected destroy$: Subject<void> = new Subject();

  public ngOnDestroy(): void {
    this.destroy$.next();
  }

  protected connect<T extends AbstractConnectableComponent>(data: ConnectableData<T>): void { 
    Object.entries(data).forEach(([key, value$]) => {
      value$.pipe(
        takeUntil(this.destroy$),
      ).subscribe(val => {
        this[key] = val;
        this.cdr.detectChanges();
      });
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)