DEV Community

Cover image for Angular and RxJS: How to actually avoid subscriptions
Dani Meier
Dani Meier

Posted on

Angular and RxJS: How to actually avoid subscriptions

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

and in the template

<li *ngFor="let customer of customers | async">{{ customer.name }}</li>
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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({}));
    }
}
Enter fullscreen mode Exit fullscreen mode

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)