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.
@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.
@Component({
templateUrl: "./example.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExampleComponent {
user$ = this.userService.user$;
constructor(private userService: UserService) {}
}
<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.
@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 valuevalue
: current value (ifready
is true)complete
: if the observable completederror
: error value or undefined
You can use those props in your template and the component class:
<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