striderhobbit
striderhobbit

Reputation: 571

Angular Subject: share only with some of the observers

Given a basic type

export interface Item {
  id: string;
}

I define a class BackupClient with a Subject:

export class BackupClient {
  private subject = new Subject<Item>();

  private share(item: Item): void {
    this.subject.next(item);
  }

  public subscribe(observer?: Partial<Observer<Item>>): Subscription {
    return this.subject.subscribe(observer);
  }
}

Via the BackupService a component ItemComponent may subscribe to my BackupClient and update its item whenever BackupClient shares a new item.

import { BackupClient } from './backup.client';
import { Injectable } from '@angular/core';

@Injectable()
export class BackupService {
  constructor() {}

  public client = new BackupClient();
}
import { BackupService } from '../backup.service';
import { Component, Input } from '@angular/core';
import { Item } from '../item';

@Component({
  selector: 'app-item',
  templateUrl: './item.component.html',
  styleUrls: ['./item.component.css'],
})
export class ItemComponent {
  constructor(private backupService: BackupService) {
    this.backupService.client.subscribe({
      next: (item) => {
        if (this.id === item.id) {
          this.item = item;
        }
      },
    });
  }

  @Input() id?: string | null;

  protected item?: Item | null;
}

In may app I now have many ItemComponent instances. By checking the id inside my observer I avoid an update of all components if only one item changes.

I would like to improve this by using some kind of Subject or multicasting design that only shares with observers of some id. That is, still all components subscribe to one Subject, but sharing is constrained to only those ItemComponents with given id.

StackBlitz

Upvotes: 1

Views: 69

Answers (1)

BizzyBob
BizzyBob

Reputation: 14750

If I'm understanding you correctly, you want subscribers to only receive emissions if the emission is for an item with a specific id.

In that case, you could modify your subscribe() method to take in the id and filter out emissions from other items:

  public subscribe(id: string, observer?: Partial<Observer<Item>>): Subscription {
    return this.subject.pipe(
        filter(item => item.id === id)
    ).subscribe(observer);
  }
this.backupService.client.subscribe(this.id, ...);

The above code won't work in the constructor, because the @Input() id will be undefined until at least the ngOnInit() lifecycle hook.


The subscribe method of your BackupClient class is probably introducing memory leaks. Each time a consumer calls .subscribe(), you create a subscription, but it doesn't appear that you have a way to ever unsubscribe from them. You may want to consider returning an Observable that consumers subscribe to, then the consumer can be responsible of unsubscribing:

public item(id: string) {
  return this.subject.pipe(
    filter(item => item.id === id)
  );
}

Then in your component:

this.backupService.client.item(this.id).subscribe(...);

Upvotes: 1

Related Questions