Maron
Maron

Reputation: 1

why am i getting this error:Type Subscription is not assignable to type FirebaseListObservable<any>?

I am trying to retrieve data from the /users list and getting this error:

Type 'Subscription' is not assignable to type 'FirebaseListObservable'. Property '$ref' is missing in type 'Subscription'.

below is my code:

import { Injectable } from '@angular/core';

import { AngularFireAuth } from 'angularfire2/auth';
import firebase from 'firebase';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable  } from 'angularfire2/database';



@Injectable()
export class FbauthserviceProvider {

  user : FirebaseListObservable<any>;   
  email: string;

  constructor(public afAuth: AngularFireAuth, public afDb: AngularFireDatabase) {
    console.log('Hello FbauthserviceProvider Provider');
  }

  signup_with_username(email: string,password: string){
    this.email = email;

    this.user = this.afDb.list('/users', {
      query: {
        orderByChild: 'email',
        equalTo: this.email,
      },
      preserveSnapshot: true
    }).subscribe(snapshots => {
      let user_data = [];
      snapshots.forEach(snapshot => {
        user_data.push(snapshot.val());
      });
      console.log(user_data);
    });
  }
}

Upvotes: 0

Views: 2554

Answers (2)

user663031
user663031

Reputation:

Your code is rife with anti-patterns and unnecessary parts.

First, don't use preserveSnapshot. You don't need to with AngularFire. You can subscribe directly to the array itself that is emitted by the FirebaseListObservable. Once you have it, there is no need to iterate over it and push its elements onto some other array--you already have the array, which you can use as is, or map, or store (but see below), or whatever. So just write:

this.user = this.afDb.list('/users', {
  query: {
    orderByChild: 'email',
    equalTo: this.email,
  }})
}).subscribe(users => {
  console.log("users are", users);
});

But there's still a problem here. this.user is not what you might thing: it will be the subscription returned by subscribe. You can't do anything with it other than unsubscribe. If you want to "unwrap" the list of users and store it as a local static property, you need to assign it in the subscribe handler, where it's available:

.subscribe(users => {
  console.log("users are", users);
  this.users = users;
})

However, unwrapping the observable and storing it in a static property is (often) an anti-pattern. The problem is that you have no idea when it's going to be ready. In the best case, you'll have to wrap places in your template where it's used with *ngIf="users", or qualify references to it in the template with the existential operator ? as in users?.length (which is not AoT-friendly).

Instead, leave the observable as is, and dereference it directly in your template using the async pipe:

this.users$ = this.afDb.list('/users', {
  query: {
    orderByChild: 'email',
    equalTo: this.email,
  }});

<div *ngFor="let user of users$ | async">
  User is {{user.name}}
</div>

Here I'm following the convention of suffixing properties representing observables with the $, which is useful to maintain your sanity.

If you only want the names, then you can map the observable itself to create an observable of arrays of names:

this.names$ = this.users$.map(users => users.map(user => user.name));

Note that these two map calls are different. The first is mapping the observable into a new observable. The second is mapping the array of users emitted by the observable into a new array containing the names. Now in your template:

<li *ngFor="let name of names$">{{name}}</li>

The discussion above relates to components. In your case, you have a service. In general, it is an anti-pattern for services to subscribe to some observable and store the value in a static local property. The problem is that components referencing that property on the service will not be updated. Instead, your service should expose observables, and let the consumer--usually a component--dereference/unwrap them.

To find users whose mobile numbers match, you can still stay in the world of observables:

this.matchingUsers$ = this.users$.map(users => users.filter(user => user.mobile === mobile));

which will create an observable of arrays of users with matching mobile numbers, which will empty if none match. Some consuming template could use this as

<div *ngFor="let matchingUser of userService.matchingUsers$ | async">
  Found matching user {{user.name}}
</div>

or

<div *ngIf="userService.matchingUsers$ | async as matchingUsers">
  <div *ngIf="!matchingUsers.length">
    Sorry, no matching users.
  </div>
</div>

and so on. Note that this as syntax requires Angular 4.

That's the Angular/observable/AngularFirebase way.

Upvotes: 1

Suraj Rao
Suraj Rao

Reputation: 29614

subscribe function returns a Subscription object.

You are assigning to a FirebaseListObservable reference. Instead subscribe after assigning if you need to reuse the observable.

this.user = this.afDb.list('/users',{

    query: {

        orderByChild: 'email',
        equalTo: this.email,
    },
    preserveSnapshot: true
})
this.user.subscribe(snapshots =>{

        let user_data = [];
        snapshots.forEach(snapshot=>{
            user_data.push(snapshot.val());
        });

Upvotes: 0

Related Questions