Search code examples
angularionic-frameworkionic2rxjs

Rx Observable not updating when other component causes its data to update and call next


In my Angular2 app I have an rx Observable which is exposed to a view. This observable is created in a store and passed to the component via subject.asObservable

I am having a problem getting the page's reference to register next() being called on the Subject if an operation on a sub-page initiates that action, but it works perfectly if its the same page which calls the store.

Below shows the layout and flow of this item, and below that I illustrate in what case it fails.

base.store, which houses the subjects next() call

  options: { collection: string };
  _subject$: Subject<Array<any>>;
  protected dataStore: {};

  constructor(protected http: Http) { }
  init(options) {
    this.options = options;
    this.dataStore = {};
    this.dataStore[options.collection] = [];
    this._subject$ = <Subject<Array<any>>>new Subject();
    this.load();
  }

  load() {
    this.http.get(endpointurl).map(response => response.json()).subscribe(
      data => {
        this.dataStore[this.options.collection] = data;
        this._subject$.next(this.dataStore[this.options.collection]);
      }),
      err => console.log("Error load", err));
  }

  addEntity(entity: any) {
    this.http.post(endpointurl, entity).subscribe(
      data => this.load(),
      err => console.log("Error addEntity", err));
  }

contacts.store, which exposes the subject asObservable

export class ContactStore extends BaseStore {

  protected _subject$: Subject<Array<Contact>>;
  options;

  constructor(protected http: Http) {
    super(http);

    this.options = { collection: "contacts" };
    this.init(this.options);
  }

  get contacts$() {
    return this._subject$.asObservable();
  }

  addOrUpdateContact(contact: Contact) {
    if(contact._id) this.updateEntity(contact);
    else this.addEntity(contact);
  }

contacts.component, which consumes the observable and displays the list

  public contacts$: Observable<Array<Contact>>;

  constructor(private contactsStore: ContactStore) {
    this.contacts$ = this.contactsStore.contacts$;
  }    

contacts.view, displays the contact

<ion-item *ngFor="let contact of contacts$ | async">{{contact.name}}</ion-item>

Now to illustrate the problem, I use the following line of code to add a new Contact; this.contactsStore.addOrUpdateContact(contact);

  • If I add a contact while contact.view is active, the view updates immediately and everything works great
  • If I add a contact on a sub-page (ex; contact.detail), when I return to the parent view the contact page doesn't reflect the newly added contact
  • If once I'm back, I then add another Contact from that parent page, the view updates and shows both the new contacts!
  • If I navigate away and return to the contacts view it also will show the new contact

I attached a subscribe event to this item everywhere in an attempt to debug. My findings seem to highlight the issue.

The subscribe from within contact.store always catches the add and emits and update. The subscribe from within contact.component does this if it's the active page, but not if the add action happens while I'm in a different component.

How can I ensure that it does, or that it refreshes itself once I return to the page?

Since it seems the issue is caused by the subscribe event not happening in the contacts.component I tried manually triggering updates with ngZone and applicationRef.tick, neither solved the issue.

To help debug I also emit an event "contacts.loaded" immediately following subject.next(). I subscribe to that event on contacts.component. It always fires even in the case when the view doesn't update. I tried putting in ChangeDetectorRef.detectChanges() to no avail.


Solution

  • I assume you provide the service more than once. Don't add it to providers: [] of @Component() but instead of @NgModule() then you only get a single service instance for the whole application, otherwise you get a service instance for each component instance.