Angularfire: General query about angularfire2 approach

Created on 15 Feb 2016  路  7Comments  路  Source: angular/angularfire

I'm not sure this is the right place to raise this query, so if there's a better place for the discussion, please point me there :)

With an angular2 app, my approach so far has been to separate out different concerns into services. So if I have a component that needs some data, I'd go and create a service with methods like:

public getNavigation(): Observable<MenuItem[]>

Methods that returns an observable of that data that my component can subscribe.
Then I can inject that re-usable service into one or more components. I can also mock that service so that my component is testable in isolation without it changing any real data.
And my components don't have a concrete dependency on Firebase, it just wants observable's of data which could come from a variety of systems and services.

So I'm not sure about the approach in this library at the moment. Do I want my components to be referencing firebase directly, or query logic and suchlike within the component? How do a write tests for this?

I think I'd rather angularfire2 just gave me mapped/synchronized objects and arrays wrapped in an observable rather than things I put directly in a component.

I'd be interested in people's thoughts.

RFC / discussion / question

All 7 comments

@mip1983 this is a fine place for such questions, since it relates to the design of the library.

I think I'd rather angularfire2 just gave me mapped/synchronized objects and arrays wrapped in an observable rather than things I put directly in a component.

There's no reason that this library couldn't be used to create providers/services for an app instead of injected into a component. In fact, using services is what will be recommended for many/most use cases. Right now the examples in README show components because it's simpler to understand.

You could, for example, create a Navigation service that would look like this:

import {Component, Injectable} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {AngularFire} from 'angularfire2';

// Navigation Service
@Injectable()
export class Navigation {
  nav:Observable<MenuItem[]>
  constructor(af:AngularFire) {
    this.nav = af.list('/navigation-items');
  }
}

@Component({
  selector: 'nav-menu',
  providers: [Navigation],
  template: `
    <ul><li *ngFor="#item of nav.nav | async">{{item.text}}</li></ul>
  `
})
export class NavMenu {
  constructor(public nav:Navigation) {
  }
}

You could even simplify it so the service itself is an Observable, with some syntax tradeoffs.

How do a write tests for this?

If injecting the AngularFire service directly into components, you could still use MockFirebase or firebase-server to send mock responses. And of course if you use services, you can just mock the service. I'm still thinking about what the recommendations and best practices for writing tests with AngularFire will be, so any feedback on challenges you see with testing would be welcome.

Does that make sense, or did I misunderstand the question?

Thanks Jeff, that did make sense and I really appreciate you taking the time explaining that, especially the example. I'm off to give it a try :)

Great! I'll resolve this issue then.

Hi @jeffbcross , thanks for your help before, it's enabled me to make some progress trying this out. I brought the code locally into my project to run it (due to the packaging problem in the other issue I raised), but that's proved to be useful as I can tweak the code.

I know this is early alpha and I'm sure a lot will change anyway, so I'm not sure how useful my feedback is, but I thought I'd share.

I ended up changing the AngularFire list function so it takes an Firebase ref rather than a string, which means I can get my data with ordering (and I presume filtering). e.g.

public getNavigation(): Observable<MenuItem[]> {
        return this.af.list(this.ref.child("navigation").orderByChild("order")) as Observable<MenuItem[]>;
}

I also changed some bits in 'firebase_list_factory.ts', so like I say it takes a ref rather than a string:

export function FirebaseListFactory(ref: any, {preserveSnapshot}: FirebaseListFactoryOpts = {}): FirebaseListObservable<any> {
    //var ref = new Firebase(absoluteUrl);
    // Would like to be strongly typed, but the ref can be Firebase or FirebaseQuery
    // Either type def's need to change or this needs to be tweaked with functions for either.

I changed the unwrapMapFn so that it includes the firebase key on the returned object:

function unwrapMapFn(snapshot: FirebaseDataSnapshot): any {
    var returnObj = snapshot.val();
    returnObj.key = snapshot.key();

    return returnObj;
}

This is required so we can preserve things like the sort order. I changed the 'onChildAdded' function so that it preserves order:

export function onChildAdded(arr: any[], child: any, prevChildKey: string): any[] {
    if (prevChildKey) {
        // Find the array index of the previous child
        var prevChildIndex = getIndexByKey(arr, prevChildKey);
        // Splice the item into the index + 1 of the previous child
        arr.splice(prevChildIndex + 1, 0, child);
    } else {
        arr.push(child);
    }

    return arr;
}

export function getIndexByKey(arr: any[], prevChildKey: string): number {    
    for (var i = 0; i < arr.length; i++) {
        if (arr[i].key() === prevChildKey) {
            return i;
        }
    }
    return null;
}

Sorry to bring up an old issue... but in all the documents, the AngularFireDatabase is injected into components. Can this be injected into a service (as a wrapper)? Eg:

import {Injectable} from '@angular/core';
import {AngularFireDatabase, FirebaseListObservable} from 'angularfire2/database';

@Injectable()
export class RegistrationService {
  visitors: FirebaseListObservable<any[]>;

  constructor(private db: AngularFireDatabase) {
    this.visitors = db.list('/visitors');
  }

  updateVisitor(key, updatedVisitor) {
    this.visitors.update(key, updatedVisitor);
  }

  createVisitor(visitor) {
    this.visitors.push(visitor);
  }

  upsertVisitor(visitor) {
    if (visitor.$key) {
      this.updateVisitor(visitor.$key, visitor);
    } else {
      this.createVisitor(visitor);
    }
  }
}

and using it in the component:


  testVistors: FirebaseListObservable<any[]>;

  constructor(private service: RegistrationService) {
    this.testVistors = service.visitors;
  }

but doing this does not seem to populate the testVisitors object. Is this a supported way, or should you inject the service directly into a component?

Hi @jdell64 -- general queries like this are best searched for first on StackOverflow. Github should be reserved for bug reports and feature requests.

You can definitely use all of AngularFire2 inside a service, injecting it inside components where needed. As for why your code isn't working....it looks like you may be missing a necessary .subscribe somewhere.

@markgoho Thanks. I thought my question was similar to the one asked, but I will do an SO question from now on.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Sun3 picture Sun3  路  3Comments

itisparas picture itisparas  路  3Comments

sharomet picture sharomet  路  3Comments

Leanvitale picture Leanvitale  路  3Comments

goekaypamuk picture goekaypamuk  路  3Comments