Angularfire: *ngFor async pipe does not work properly when offline

Created on 21 Feb 2017  路  5Comments  路  Source: angular/angularfire

Version info

Angular:2.4.8

Firebase:3.6.9

AngularFire:2.0.0-beta.8

Chrome 56.0.2924.87 and win10

How to reproduce these conditions

first way of reproducing the behaviour
```ts
import { Component, Inject, NgModule, Input } from '@angular/core'
import { AngularFire, FirebaseApp, FirebaseListObservable, AuthProviders, AuthMethods } from 'angularfire2';

@Component({
selector: 'my-people',
template: `


Image: {{ img?.name }} {{ img?.$key }}

<div *ngFor="let obj of  objectives | async">
objective:  {{ obj ?.name }} {{ obj?.$key }}
</div>

`,
})
export class PeopleComponent {
images: any[];
uid: string;
objectives: FirebaseListObservable constructor(public af: AngularFire) {

this.af.auth.subscribe(auth => {
  if (auth) {
    this.uid = auth.uid;

    this.objectives = this.af.database.list(`/users/${this.uid}/objectives`);
    this.af.database.list(`/users/${this.uid}/images`).subscribe(data => {
      this.images = data;

    });

  }
});

}
}
**second way of reproducing the behaviour** ts
import { Component, Inject, NgModule, Input } from '@angular/core'
import { AngularFire, FirebaseApp, FirebaseListObservable, AuthProviders, AuthMethods } from 'angularfire2';

@Component({
selector: 'my-people',
template: <div *ngFor="let obj of objectives | async"> objective: {{ obj ?.name }} {{ obj?.$key }} </div> ,
})
export class PeopleComponent {
images: any[];
uid: string;
objectives: FirebaseListObservable constructor(public af: AngularFire) {

this.af.auth.subscribe(auth => {
  if (auth) {
    this.uid = auth.uid;

    this.objectives = this.af.database.list(`/users/${this.uid}/objectives`);
    // this.af.database.list(`/users/${this.uid}/objectives`).subscribe(data => {
    //nothing here
    //but somehow this make it working offline   
    //});


  }
});

}
}
```

Steps to set up and reproduce

the goal here was to test the offline behaviour of angularfire2 and I was surprised to see that when I went offline reloading a component with an ngFor async on the FirebaseListObservable would erase my bindings except if go though the subscribe method.

first way of reproducing the behaviour

  • retrieve two different FirebaseListObservable from firebase (ex: images and objectives)
  • susbcribe to one directly with the async pipe in an ngFor
  • susbcribe to the other list with the subscribe() method and assign it to a property in the component then go through the list with an ngFor. So there is no need for the the async pipe in this ngFor
  • then when you launch the app the component is called then you should see the two lists
  • disconnect from the network . I used the offline checkbox in chrome dev tools
  • reload the component without reloading the full page
    -- to do so I simply go to another tab and then come back again
  • you will only see one list

second way of reproducing the behaviour
this also happen if you follow these steps

  • retrieve the same FirebaseListObservable from firebase (ex: objectives)
  • susbcribe to it directly with the async pipe in an ngFor
  • then when you launch the app the component is called then you should see the list
  • disconnect from the network . I used the offline checkbox in chrome dev tools
  • reload the component without reloading the full page
    -- to do so I simply go to another tab and then come back again
  • nothing appears. the list doesn't show
  • then you redo the steps above but prior to that add a subscribe method to the FirebaseListObservable that does nothing.
  • offline you will see the list appear

Sample data and security rules

Security open for all !

Debug output

* Errors in the JavaScript console *

* Output from firebase.database().enableLogging(true); *

* Screenshots *
first way of reproducing the behaviour
offline
online

second way of reproducing the behaviour
offline not working
offline working

Expected behavior

when offline the async pipe binding should work without the use of calling the subscribe method

Actual behavior

offline the async pipe binding does not work without the use of calling the subscribe method

Most helpful comment

Okay, this is what I believe is happening:

  • You call subscribe but you never call unsubscribe on the returned subscription.
  • Internally, that means the observable's implementation holds listeners that were added using the internal Firebase ref's on method.
  • Those listeners are not switched off when the component is destroyed (as unsubscribe is not called).
  • When a new component is created (after your disabling of the network) a new Firebase ref is created and new listeners are added for the new observable.
  • However, there is internal caching in the Firebase SDK and the fact that the listeners related to the first component's observable were not switched off means that this internal cache is able to be used.
  • I suspect that if you implement OnDestroy in the component and call unsubscribe on the subscription returned by subscribe, the cache won't be used and it will behave the same as with the async pipe.

All 5 comments

Is the tab component using the RouteReuseStrategy or something to keep the components from being torn down? If so, the list that uses subscribe would have a cached copy in the component - that is, in the property that's assigned to inside the subscribe. When change detection is triggered, the subscribed component would have the cached list and the other would have an observable that would not emit anything.

If that's what's happening, it's the expected behaviour, I guess. Otherwise, I think you'll need to create a Plunk - using the template - for this to be investigated.

@cartant if you take a look at the second way to reproduce the behavious
I don't do anything inside the subscribe method so I don't think something is cached.
I am simply using material2 tab nav bar like this

<nav md-tab-nav-bar>
       <a md-tab-link routerLink="/login" routerLinkActive #rla="routerLinkActive" [active]="rla.isActive">Login</a>

Okay, this is what I believe is happening:

  • You call subscribe but you never call unsubscribe on the returned subscription.
  • Internally, that means the observable's implementation holds listeners that were added using the internal Firebase ref's on method.
  • Those listeners are not switched off when the component is destroyed (as unsubscribe is not called).
  • When a new component is created (after your disabling of the network) a new Firebase ref is created and new listeners are added for the new observable.
  • However, there is internal caching in the Firebase SDK and the fact that the listeners related to the first component's observable were not switched off means that this internal cache is able to be used.
  • I suspect that if you implement OnDestroy in the component and call unsubscribe on the subscription returned by subscribe, the cache won't be used and it will behave the same as with the async pipe.

and the winner is @cartant :sunglasses: when implementing the unsubscribe the data is not cached.
thanks so much for your help
by the way I will implement a RouteReuseStrategy . This way I can use the async pipe (and let it subscribe unsubscribe ) and when I will be navigating the component will not be destroyed.

btw you can close this issue

Was this page helpful?
0 / 5 - 0 ratings