We all know it - we shouldn’t subscribe to observables directly. Instead, we should use the async pipe. It all totally makes sense, but then we leave our comfy reading chair, sit at our desk again, start coding… and subscribing. There are situations that aren’t just that obvious.
Our example
We’re displaying a list of customers.
export class CustomerComponent implements OnInit {
customers: Observable<Customer[]>;
constructor(private customerService: CustomerService) {
}
ngOnInit() {
this.customers = this.customerService.getAll();
}
}
and in the template
<li *ngFor="let customer of customers | async">{{ customer.name }}</li>
Well, that was easy. No subscribe here, yay! Angular is handling the subscription for us with its async pipe.
Ok now let’s move on. We want to add new customers to the list. So we introduce a little form-component and emit an event to our CustomerComponent when a new entry gets submitted. We save the new entry and after that, we’re reloading the whole list. Shouldn’t be that hard.
onCustomerCreated(customer: Customer) {
this.customerService.create(customer).subscribe(() =>
this.customers = this.customerService.getAll());
}
Ok, one subscribe, but that seems reasonable since we want to wait until the create-action completes. But mh… that’s not working because we’re replacing the reference on customers
. Ok, every problem can be fixed with a subject:
export class CustomerComponent implements OnInit {
customers = new BehaviorSubject<Customer[]>([]);
constructor(private customerService: CustomerService) {
}
ngOnInit() {
this.loadCustomers()
}
onCustomerCreated(customer: Customer) {
this.customerService.create(customer)
.subscribe(() => this.loadCustomers());
}
private loadCustomers() {
this.customerService.getAll().subscribe(
loadedCustomers => this.customers.next(loadedCustomers));
}
}
This seems fine. But - we’re subscribing to the load action again, which is feeling wrong. And it is wrong. But in one thing we were right, we can fix it with a subject:
export class CustomerComponent implements OnInit {
customers: Observable<Customer[]>;
private readonly refreshTrigger = new BehaviorSubject({});
constructor(private customerService: CustomerService) {
}
ngOnInit(): void {
this.customers = this.refreshTrigger.pipe(
switchMap(() => this.customerService.getAll()));
}
onCustomerCreated(customer: Customer): void {
this.customerService.create(customer)
.subscribe(() => this.refreshTrigger.next({}));
}
}
Now we aren’t using the subject as the source. Instead, we are using it as a trigger to load the customer-data. Like this, we can use the same observable-instance as source and push new data in it, if a refresh gets triggered. And we still can rely on the async-pipe to manage our subscription.
Top comments (0)