Skip to content

Handling Observables with Angular Rx Hook

We build an Angular Hook that handles Observables in the component class. It will subscribe to the observable, provide you the value and status and automatically unsubscribe when the component is destroyed.

Published
Jan 24, 2022
Updated
Jan 24, 2022
Reading time
3 min read

This is a subpost of React Hooks for Angular. Please read that post first to understand what Angular Hooks are.


As Angular comes with RxJS, you will often have some observables provided by a service that you want to use in your component. You need to subscribe to the observable to get the values.

There are multiple ways to do this. We take a look at existing solutions and then build a hook to manage the subscription.

Subscribe in component

You can subscribe in the component class but this has two main drawbacks: you need to call markForCheck() whenever you receive a new value and you need to unsubscribe from the observable when the component is destroyed. The latter in particular is easy to forget.

/example.component.ts
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExampleComponent implements OnInit, OnDestroy {
  user?: User;

  private subscription?: Subscription;

  constructor(
    private userService: UserService, 
    private cdr: ChangeDetectorRef
  ) {}

  ngOnInit() {
    this.subscription = this.userService.user$.subscribe((user) => {
      this.user = user;
      this.cdr.markForCheck();
    });
  }

  ngOnDestroy() {
    // Don't forget to unsubscribe
    this.subscription?.unsubscribe();
  }
}

Subscribe in template

That's why the recommended approach is using *ngIf together with the async pipe in your template. The pipe will subscribe and unsubscribe automatically.

/example.component.ts
@Component({
  templateUrl: "./example.component.html",
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExampleComponent {
  user$ = this.userService.user$;
  constructor(private userService: UserService) {}
}
/example.component.html
<div *ngIf="user$ | async">Hello {{ user.name }}</div>

This pattern is not very flexible as it does not work if the observable emits falsy values and handling loading, error and complete state is very cumbersome to impossible. There are directives specific for observables that do a better job, like ngrxLet.

RxHook

Now let's use a hook to manage the subscription. You can provide an observable to the use() function of the hook. The hook will subscribe to the observable and unsubscribe when the component is destroyed.

/example.component.ts
@Component({
  templateUrl: "./example.component.html",
  providers: [RxHook],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
  counter$ = this.rx.use(
    interval(1000).pipe(map(i => (i + 1).toString())))
  );

  constructor(private rx: RxHook) {}
}

The use() function returns an object with the following properties:

  • ready: if the observable has a value
  • value: current value (if ready is true)
  • complete: if the observable completed
  • error: error value or undefined

You can use those props in your template and the component class:

/example.component.html
<p *ngIf="!counter$.ready">Counter starts soon</p>
<p *ngIf="counter$.ready">
  Counter: {{ counter$.value }}
</p>

This is just a basic implementation of the hook. There are a lot more things to implement, like:

  • providing an initial value
  • lazy subscription (subscribe when the observable is used for the first time)
  • handling loading, error and complete state

We could even build something like react-query with queries and mutations.

Demo

Try a simple demo of the hook:
angular-hooks.blog.sandroroth.com

The full source code is available on GitHub:
@rothsandro/angular-hooks