5

I have simple app that inserts new record to Firebase and below the input field it simply lists last 10 items in database.

PROBLEM:

  1. I need to start inputing someting to input field or to click on "update" for data from Firebase to appear in my listing. It seems that data comes in after the processing by angular has been already done, causing it now to show the list initialy. I tried adding "| async" to ngFor, but does not work and causes errors in console. Everything is working good, just need to load and show the data onload, without me inputing a keystroke in input or clicking to update button.
  2. when I open two tabs with same app, they do not update realtime as I supposed they would once I start inputing data zig-zag from one tab to another, I only appears in the tab in which I inserted the data.

import {Component,enableProdMode} from 'angular2/core'; enableProdMode();
import {FirebaseService} from 'firebase-angular2/core';
var myFirebaseRef = new Firebase("https://xxx.firebaseio.com/messages");

@Component({
    selector: 'my-app',
    template: `
        <input type=text [(ngModel)]="newmsg" #newmsgref (keyup.enter)="updateHello(newmsgref)">
        <button (click)="updateHello(newmsgref)">Update</button>
        <ul>
            <li *ngFor="#item of items">
              {{item}}
            </li>
        </ul>
    `
})
export class AppComponent {
    newmsg:string;
    items:Array<string>=[];

    constructor() {
        myFirebaseRef.limitToLast(10).on('child_added', (childSnapshot, prevChildKey) => {
            childSnapshot.forEach((records) => {
              this.items.push(records.val());
            });
        });
    }

    updateHello(r){
        myFirebaseRef.push({
            title: this.newmsg
        });
        this.newmsg="";
        r.focus();
    }
}

SOLUTION #1 WITH NGZONE MANUAL UPDATE WHICH WORKED (thanks to: Günter Zöchbauer) Since the definition was outside the A2, the use of NgZone is really easy and corrected the problem. Thanks Günter ;)

import {Component,enableProdMode,NgZone} from 'angular2/core'; enableProdMode();
import {FirebaseService} from 'firebase-angular2/core';
var myFirebaseRef = new Firebase("https://xxx.firebaseio.com/messages");

@Component({
    selector: 'my-app',
    template: `
        <input type=text [(ngModel)]="newmsg" #newmsgref (keyup.enter)="updateHello(newmsgref)">
        <button (click)="updateHello(newmsgref)">Update</button>
        <ul>
            <li *ngFor="#item of items">
              {{item}}
            </li>
        </ul> 
    `
})
export class AppComponent {
    newmsg:string;
    items:Array<string>=[];

    constructor(private zone: NgZone) {
        myFirebaseRef.limitToLast(10).on('child_added', (childSnapshot, prevChildKey) => {
            zone.run(() => {
                childSnapshot.forEach((records) => {
                    this.items.push(records.val());
                });
            });
        });
    }

    updateHello(r){
        myFirebaseRef.push({
            title: this.newmsg
        });
        this.newmsg="";
        r.focus();
    }
}

SOLUTION #2 AUTOMATIC A2 UPDATING WHICH WORKED (thanks to: Thierry) I simply put the definition inside the class of angular2, not outside, and everything started working as I assumed initialy. Thank you Thierry for the insight ;).

import {Component,enableProdMode} from 'angular2/core'; enableProdMode();
import {FirebaseService} from 'firebase-angular2/core';

@Component({
    selector: 'my-app',
    template: `
        <input type=text [(ngModel)]="newmsg" #newmsgref (keyup.enter)="updateHello(newmsgref)">
        <button (click)="updateHello(newmsgref)">Update</button>
        <ul>
            <li *ngFor="#item of items">
              {{item}}
            </li>
        </ul>
    `
})
export class AppComponent {
    newmsg:string;
    items:Array<string>=[];
    myFirebaseRef = new Firebase("https://xxx.firebaseio.com/messages");

    constructor() {
        this.myFirebaseRef.limitToLast(10).on('child_added', (childSnapshot, prevChildKey) => {
            childSnapshot.forEach((records) => {
                this.items.push(records.val());
            });
        });
    }

    updateHello(r){
        this.myFirebaseRef.push({
            title: this.newmsg
        });
        this.newmsg="";
        r.focus();
    }
}
Milan Milanovic
  • 433
  • 1
  • 7
  • 14
  • Your first example was super helpful for me to figure out that *purposefully* instantiating the Firebase ref outside of any Angular component/service would let Firebase run without it triggering change detection left and right! – tobek Jan 04 '17 at 03:45

2 Answers2

6
constructor(private zone: NgZone)

...
zone.run(() => {
  childSnapshot.forEach((records) => {
    this.items.push(records.val());
  });
});
...

See also Triggering Angular2 change detection manually

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Your anwer is correct, will accept answer in 5 minutes (I cant do it sooner). There is problem now that the input field is also "mirrored" but I do not want that to happen. How can I leave the syncing only to the UL>LI elements? – Milan Milanovic Feb 03 '16 at 12:44
  • Not sure what you mean by "mirrored". Mirrored from where to where? Maybe you want change `[(ngModel)]="newmsg"` to something like `[ngModel]="newmsg" (blur)="newmsg=$event"` to only update `newmsg` when when you leave the input or `[ngModel]="newmsg" (blur)="doSomething($event)"` for custom action when the input is left. – Günter Zöchbauer Feb 03 '16 at 12:47
  • I investigated the "mirroring". It only behaves that way when I have two windows open in any browser on "localhost:3000". When I type something in INPUT field it is synced letter by letter to the same INPUT in other browser opened on localhost:3000. Can't explain. On firebase hosting all works good. Thx for help. – Milan Milanovic Feb 03 '16 at 13:08
5

Günter exactly points the problem out ;-) In fact the myFirebaseRef is created outside Angular2 processing.

If you're interested in a custom pipe for Firebase you could have a look at the source for the Sarah Robinson's talk at Angular Connect 2015:

Hope it helps you, Thierry

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Both solutions are great, depending on specific case. Thanks for the insight for putting the myFirebaseRef inside the A2 so it works as I assumed. I will try to make that great pipe work if possible ;). Thx. – Milan Milanovic Feb 03 '16 at 13:27
  • Pipe works like a charm ;). I just need to figure out how to use Firebase filters on it, so it lists ie. last 20 items and not all there is in that table... – Milan Milanovic Feb 03 '16 at 13:39
  • Awesome ;-) Pleased to hear that! Perhaps you could consider to use this: https://www.firebase.com/docs/web/api/query/limittolast.html. – Thierry Templier Feb 03 '16 at 13:41
  • I know the function, but I do not know how to put it in template ngFor so that it works without changing the pipe definition itself. I tried something like this: #itm of myFirebaseUrl | firebaseevent:'child_added' | limitToLast:10 but id does not work, and also native A2 does not work: #itm of myFirebaseUrl | firebaseevent:'child_added' | limitTo:10 . It would be great if I could use Firebase functions directly on the ngFor ;) – Milan Milanovic Feb 03 '16 at 13:48
  • I think you need to update a bit the pipe ;-) because the `limitToLast` function must be called before the `on` one. That said you could refactor a bit the pipe to have something like that: `#itm of myFirebaseUrl | firebaseevent:{name:'child_added', limitToLast:10}`... – Thierry Templier Feb 03 '16 at 13:58
  • will try to refactor it when I get some free time. Thanks for great idea and good point considering the sequence of events and the formatting of the pipe. Thx! – Milan Milanovic Feb 03 '16 at 14:20