3

I have a service that fetches some objects via firebase. It then populates an array which should then prompt angular to update the DOM with an *ngFor statement, but it doesn't work. Code as follows:

TeachersService

    import {Injectable} from 'angular2/core';
import {Teacher} from './teacher.ts';

@Injectable()
export class TeachersService {

  private firebaseURL: string;
  private teachersRef: Firebase;

  constructor() {
    this.firebaseURL = "https://xyz.firebaseio.com/teachers";
    this.teachersRef = new Firebase(this.firebaseURL);
  }

  getTeachers() {

    this.teachersRef.on("value", function(snapshot) {

      var tempArr = new Array(snapshot.numChildren());
      // this.teachers = snapshot.val();
      snapshot.forEach(function(data) {
        var teacher = {
          name: data.key(),
          position: data.val().stilling
        }
        tempArr.push(teacher);
      });
      // this.teachers = tempArr;
      console.log(tempArr);
      return Promise.resolve(tempArr);
    });

    return Promise.resolve([]);
  }
}

And Teachers

import {Component, AfterViewInit, OnInit, View} from 'angular2/core';

import {NgFor} from 'angular2/common';
import {Router} from 'angular2/router';

import {TeachersService} from '../../services/teachers.service';

@Component({
  selector: 'teachers',
  templateUrl: './components/teachers/teachers.html',
  providers: [TeachersService]
})


export class TeachersCmp implements AfterViewInit, OnInit {

  private firebaseURL: string;
  private teachersRef: Firebase;
  public teachers: Array<any>;
  constructor(
    private _router: Router,
    private _teachersService: TeachersService
  ) {}

  ngOnInit() {
    this.populateTeachersArr();
  }

  populateTeachersArr() {
    this._teachersService.getTeachers().then(teachers => this.teachers = teachers);
  }

}

Teachers.html

<ul>
<li *ngFor="#teacher of teachers">
        <strong>{{teacher.name}}</strong>: {{teacher.position}}
      </li>

I suspect this has to do with change detection (or at least my understanding of it). But I don't know how to prompt ng2 to detect the array update. If I create a static array like: [1,2,3,4] the *ngFor statement correctly works and displays it like it should in the DOM.

Chris
  • 7,830
  • 6
  • 38
  • 72
  • Looks similar to http://stackoverflow.com/questions/34592857/view-is-not-updated-on-change-in-angular2/34593821#34593821 – Günter Zöchbauer Jan 04 '16 at 19:16
  • It definately does @GünterZöchbauer. But being a bit thick, could you possibly provide an example of how to implement that? – Chris Jan 04 '16 at 19:22
  • I think the problem comes from the way you use promises within the `getTeachers` function (see my answer) and isn't related to change detection... – Thierry Templier Jan 04 '16 at 21:21

1 Answers1

5

I find a bit strange the way you use promises. It should be the reason of your problem. As a matter of fact, you return a promise resolved with an empty array. This array is directly used within the then method on the other side. When the value event is received, you return another promise within the callback which is never used.

I think you should return a promise and resolve it when the values are received within the callback for the value event.

Here is the refactoring I would propose within the getTeachers method to fix your problem:

getTeachers() {
  return new Promise((resolve, reject) => {
    this.teachersRef.on("value", function(snapshot) {
      var tempArr = new Array(snapshot.numChildren());
      snapshot.forEach(function(data) {
        var teacher = {
          name: data.key(),
          position: data.val().stilling
        }
        tempArr.push(teacher);
      });
      resolve(tempArr);
    });
  });
}

Edit

After having some tests with your code on Firebase, it found out where the problem is. In fact, using promises isn't possible here since they are only called once. I mean the callback specified within the then method will be called only for the first value event. This is a drawback of promises.

To go over this restriction, you need to use observables. Here is the refactored version of the getTeachers method:

import {Injectable} from 'angular2/core';
(...)
import {Observable} from 'rxjs/Rx';

@Injectable()
export class TeachersService {
  (...)
  getTeachers() {
    return new Observable(observer => {
      this.teachersRef.on("value", snapshot => {
        var tempArr = [];
        snapshot.forEach(function(data) {
          var teacher = {
            name: data.key(),
            position: data.val().stilling
          }
          tempArr.push(teacher);
        });
        observer.next(tempArr);
      }
    });
  }

Within the component you can call the getTeachers method like this:

@Component({
  (...)
  template: `
    (...)
    <ul>
      <li *ngFor="#teacher of teachers">
        <strong>{{teacher.name}}</strong>
      </li>
    </ul>
  `,
  providers: [ TeachersService ]
})
export class AppComponent {
  (...)

  ngOnInit() {
    this.populateTeachersArr();
  }

  populateTeachersArr() {
    this._teachersService.getTeachers().subscribe(teachers =>
      this.teachers = teachers;
    );
  }
}

or go further by leveraging the async pipe (which will directly manages the observable object):

@Component({
  (...)
  template: `
    (...)
    <ul>
      <li *ngFor="#teacher of (teachers | async)">
        <strong>{{teacher.name}}</strong>
      </li>
    </ul>
  `,
  providers: [ TeachersService ]
})
export class AppComponent {
  (...)

  ngOnInit() {
    this.populateTeachersArr();
  }

  populateTeachersArr() {
    this.teachers = this._teachersService.getTeachers();
  }
}

Hope it helps you, Thierry

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Hi Thierry , thanks for your response. I'm afraid this produces the same outcome, i.e. no displayed data. I think that Gunther's proposal that it might have to do with the same issue described here: http://stackoverflow.com/questions/34592857/view-is-not-updated-on-change-in-angular2/34593821#34593821 is correct, I just don't know how to implement it. – Chris Jan 05 '16 at 17:30
  • I'll try to reproduce your problem with Firebase tomorrow... I'll keep you informed ;-) – Thierry Templier Jan 05 '16 at 19:27
  • I think the solution that works on my side ;-) It's because of a limitation of promises. You should use observables instead. I updated my answer with working code. Feel free to tell if it fixes your problem. – Thierry Templier Jan 06 '16 at 15:25