Angularfire: FirebaseListObservable emits duplicate values for first insert

Created on 8 Mar 2016  路  23Comments  路  Source: angular/angularfire

If I subscribe to an empty list, and then add a new value to that list it looks like that value is getting emitted twice. For example if I do:

let observable = this._angularFire.list('my_path', {preserveSnapshot: true});
observable.subscribe ((l) => {
  console.log('length', l.length);
});
setTimeout(() => {
  observable.add({text: 'new'});
}, 1);

If 'my_path' is initially empty this will output:
length 1
length 2

The 2 is erroneous and contains a duplicate entry.

It doesn't look like this problem occurs if 'my_path' is not initially empty.

Firebase(List | Object) bug

Most helpful comment

Hi @davideast, I'm facing the same problem described by doovers.
Here's what I'm doing (it can easily be my fault since I'm still new to this):

---test.component.ts :

import {Component} from 'angular2/core';
import {AngularFire, FirebaseListObservable} from 'angularfire2';

@Component({
    selector: 'test',
    templateUrl: 'app/test.component.html'
})

export class TestComponent{
    private _testList: FirebaseListObservable<any[]>;

    constructor(private _af: AngularFire) {
        this._testList = _af.database.list('/test');
    }

    addNew(item: any){
        this._testList.push(item);
    }

    updateMe(item: any){
        this._testList.update(item, { value: 5});
    }

    removeMe(item: any){
        this._testList.remove(item);
    }
}

---test.component.html :

<ul *ngFor="#item of _testList | async">
  <li class="text">
    <p>Value: {{ item.value }}</p>
    <button type="button" (click)="updateMe(item)">Update me!</button>
    <button type="button" (click)="removeMe(item)">Remove me!</button>
  </li>
</ul>

<form #form="ngForm" (ngSubmit)="addNew(form.value)" novalidate>
  <label>Value:</label>
  <input type="text" ngControl="value" required>
  <button type="submit" [disabled]="!form.form.valid">Add</button>
</form>

If you try to update the first item of the list, it will show both the old value and the new updated value (5), and if there was only one item, every new item inserted from now on is duplicated; if you update every other item, it seems to work fine. The server always shows the correct results.

All 23 comments

@davideast I'll take care of this one unless you've started work.

@jteplitz602 I can't reproduce this. I copied your reproduction into a test, and I'm seeing length 0 and length 1, which are expected since the observable emits an empty array the first time if the path is empty. I'll leave this open in case I'm missing something.

Closed

I'm having the same issue but I also get it when I modify a record. So when I change a property on an item in the list, as soon as the list is updated it now contains the item in the item in both the old and new state. The duplicate does not exist on the server, only the new object and it is cleared on reload.

@doovers Hmmm... 馃槙 This shouldn't be the case. Can you provide a code sample to reproduce?

Hi @davideast, I'm facing the same problem described by doovers.
Here's what I'm doing (it can easily be my fault since I'm still new to this):

---test.component.ts :

import {Component} from 'angular2/core';
import {AngularFire, FirebaseListObservable} from 'angularfire2';

@Component({
    selector: 'test',
    templateUrl: 'app/test.component.html'
})

export class TestComponent{
    private _testList: FirebaseListObservable<any[]>;

    constructor(private _af: AngularFire) {
        this._testList = _af.database.list('/test');
    }

    addNew(item: any){
        this._testList.push(item);
    }

    updateMe(item: any){
        this._testList.update(item, { value: 5});
    }

    removeMe(item: any){
        this._testList.remove(item);
    }
}

---test.component.html :

<ul *ngFor="#item of _testList | async">
  <li class="text">
    <p>Value: {{ item.value }}</p>
    <button type="button" (click)="updateMe(item)">Update me!</button>
    <button type="button" (click)="removeMe(item)">Remove me!</button>
  </li>
</ul>

<form #form="ngForm" (ngSubmit)="addNew(form.value)" novalidate>
  <label>Value:</label>
  <input type="text" ngControl="value" required>
  <button type="submit" [disabled]="!form.form.valid">Add</button>
</form>

If you try to update the first item of the list, it will show both the old value and the new updated value (5), and if there was only one item, every new item inserted from now on is duplicated; if you update every other item, it seems to work fine. The server always shows the correct results.

Have the same issue

<ul *ngFor="#item of items | async">
  <li class="text">
    {{item.$value}}
  </li>
</ul>
this.items = af.database.list("/items");

When I try to update an item from Firebase.com DB studio, I get dublicates listed in a view.

@davideast Hi David sorry for the late response! I haven't had time to look at this until now. I've tried to recreate the problem in a small demo app but haven't been successful so far. It must be something specific I'm doing in my main project. I'll keep digging and report back when I figure it out!

Same issue here, only happens for the first item(add/update), there will be a new value with an old value pops up, and the old value disappears when page gets refreshed

140 will address this 馃敡

I am having the same issue with my sample application at http://taotasks.com.

Visit the website and login with google, then add a few tasks, then click a task and update some fields, when it goes back to the list, the observable has emitted multiple duplicates.

Tasks List
`import { Component, OnInit } from '@angular/core';
import { RouterModule, Router } from '@angular/router';
import { AngularFire, FirebaseListObservable } from 'angularfire2';

import { AF } from './providers/af';

import { Task } from './task';
import { TaskDetailComponent } from './task-detail.component';
import { TaskService } from './task.service';

@Component({
selector: 'tao-tasks',
moduleId: module.id,
templateUrl: './tasks.component.html',
styleUrls: ['./tasks.component.css']
})

export class TasksComponent implements OnInit {
tasks: FirebaseListObservable title = 'Tao Tasks';
selectedTask: Task;
af: AngularFire;

constructor(private taskService: TaskService,
    private router: Router, af: AngularFire, private afService: AF) {
    this.af = af;
}

getTasks(): void {
    this.tasks = this.taskService.getTasks();
}


ngOnInit(): void {

    this.afService.af.auth.subscribe(
        (auth) => {
            if (auth == null) {
                console.log('Not Logged in.');
                this.router.navigate(['login']);
            }
            this.getTasks();
        });

}

gotoDetail(task): void {
    this.router.navigate(['/detail', task.$key]);
}

add(name: string): void {
    name = name.trim();
    if (!name) { return; }
    var key = this.taskService.create(name);
    this.selectedTask = null;
}

delete(task): void {
    this.taskService
        .delete(task.$key)
        .then(() => {
            //todo: this.tasks = this.tasks.filter(h => h !== task);
            if (this.selectedTask === task) { this.selectedTask = null; }
        });
}

}
`

Task Details
<div *ngIf="task"> <fieldset class="task-details"> <h2>Details</h2> <h3>{{task.name}}</h3> <div> <label for="name">Name </label> <input name="name" [(ngModel)]="task.name" placeholder="name" /> </div> <div> <label for="description">Description </label> <input name="description" [(ngModel)]="task.description" placeholder="description" /> </div> <div> <label for="date">Date </label> <input name="date" type="date" [(ngModel)]="task.date" placeholder="date" /> </div> <div> <label for="time">Time </label> <input name="time" type="time" [(ngModel)]="task.time" placeholder="time" /> </div> <button (click)="goBack()">Back</button> <button (click)="save()">Save</button> </fieldset> </div> <hr>

Components
Task List Components.
`import { Component, OnInit } from '@angular/core';
import { RouterModule, Router } from '@angular/router';
import { AngularFire, FirebaseListObservable } from 'angularfire2';

import { AF } from './providers/af';

import { Task } from './task';
import { TaskDetailComponent } from './task-detail.component';
import { TaskService } from './task.service';

@Component({
selector: 'tao-tasks',
moduleId: module.id,
templateUrl: './tasks.component.html',
styleUrls: ['./tasks.component.css']
})

export class TasksComponent implements OnInit {
tasks: FirebaseListObservable title = 'Tao Tasks';
selectedTask: Task;
af: AngularFire;

constructor(private taskService: TaskService,
    private router: Router, af: AngularFire, private afService: AF) {
    this.af = af;
}

getTasks(): void {
    this.tasks = this.taskService.getTasks();
}


ngOnInit(): void {

    this.afService.af.auth.subscribe(
        (auth) => {
            if (auth == null) {
                console.log('Not Logged in.');
                this.router.navigate(['login']);
            }
            this.getTasks();
        });

}

gotoDetail(task): void {
    this.router.navigate(['/detail', task.$key]);
}

add(name: string): void {
    name = name.trim();
    if (!name) { return; }
    var key = this.taskService.create(name);
    this.selectedTask = null;
}

delete(task): void {
    this.taskService
        .delete(task.$key)
        .then(() => {
            //todo: this.tasks = this.tasks.filter(h => h !== task);
            if (this.selectedTask === task) { this.selectedTask = null; }
        });
}

}
`

Task Details Component.
`import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';

import 'rxjs/add/operator/switchMap';

import { TaskService } from './task.service';
import { Task } from './task';
@Component({
moduleId: module.id,
selector: 'my-task-detail',
templateUrl: './task-detail.component.html',
styleUrls: ['./task-detail.component.css'],
})

export class TaskDetailComponent implements OnInit {
task;

constructor(
private taskService: TaskService,
private route: ActivatedRoute,
private location: Location
) { }

ngOnInit(): void {
this.route.params.switchMap((params: Params) => this.taskService.getTask(params['id']))
.subscribe(task => this.task = task);
}

goBack(): void {
this.location.back(); // Make more defensive to not exit app.
}

save(): void {
this.taskService.update(this.task)
.then(() => this.goBack());
}
}
`

Here is my data layer.

`import { Injectable } from '@angular/core';
import { AngularFire, AuthProviders, FirebaseListObservable, FirebaseObjectObservable, AuthMethods } from 'angularfire2';

import { Task } from '../task';

@Injectable()
export class AF {
auth;
tasksUrl='';
rootUrl='';

constructor(public af: AngularFire) { }
/**

  • Logs in the user
  • @returns {firebase.Promise}
    /
    loginWithGoogle() {
    return this.af.auth.login({
    provider: AuthProviders.Google,
    method: AuthMethods.Popup,
    });
    }
    /
    *
  • Logs out the current user
    */
    logout() {
    return this.af.auth.logout();
    }

setUserInfo(displayName, email, photoURL ){
let user = this.af.database.object(${this.rootUrl}/${this.auth.uid});
user.update({
displayName: displayName,
email: email,
photoURL: photoURL
});
}

getTasks() {
return this.af.database.list(this.tasksUrl);
}

getTaskByKey(key: string) {
return this.af.database.object(${this.tasksUrl}/${key});
}

// Returns the key of the task
addTask(name: string) {
return this.af.database.list(this.tasksUrl).push({ name: name }).key;
}

updateTask(task) {
const dbTask = this.getTaskByKey(task.$key);
const model = new Task();
model.date = task.date == null ? null : task.date;
model.description = task.description == null ? null : task.description;
model.name = task.name == null ? null : task.name;
model.time = task.time == null ? null : task.time;
return dbTask.update(model);
}

deleteTask(key: string) {
return this.af.database.list(this.tasksUrl).remove(key);
}

search(term): FirebaseListObservable console.log("Search Term is " + term);
return this.af.database.list(this.tasksUrl, {
query: {
orderByKey: true,
equalTo: term,
}
});
}

getDashboardTasks() {
return this.af.database.list(this.tasksUrl, {
query: {
limitToLast: 4,
orderByKey: true
}
});
}
}
`

And here is app.component.ts
`import { Component } from '@angular/core';
import { AF } from './providers/af';
import { Router } from '@angular/router';

@Component({
moduleId: module.id,
selector: 'tao-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})

export class AppComponent {
title = 'Tao Tasks';
public isLoggedIn: boolean;
public isCollapsed: false;
public auth;

constructor(public afService: AF, private router: Router) {
this.afService.af.auth.subscribe(
(auth) => {
if (auth == null) {
console.log('Not Logged in.');
this.router.navigate(['login']);
this.isLoggedIn = false;
} else {
this.auth = auth;
this.afService.auth = auth;
this.afService.tasksUrl = /users/${auth.uid}/tasks;
this.afService.rootUrl = '/users';
this.afService.setUserInfo(auth.google.displayName, auth.google.email, auth.google.photoURL);
console.log('UID: ' + auth.uid );
console.log('Successfully Logged in.');
this.isLoggedIn = true;
this.router.navigate(['']);
}
}
);
}

logout() {
this.afService.logout();
}

public collapsed(event: any): void {
console.log(event);
}

public expanded(event: any): void {
console.log(event);
}
}
`

This issue seems to have cropped up again?

DB is structured as:

/docs/[userId]/[docId]/...

I have a page that is subscribed to a particular user's doc via database.list('/docs/123').subscribe When I update a doc (or multiple) via database.object('/docs/123/xyz').update(...), my list subscription returns duplicates. Specifically, one copy returns with only the object data:

{
 prop1: ...
 prop2: ...
 ...
}

While the other copy returns decorated as usual:

{
  $key: xyz,
  $exists: ...
  prop1: ...
  prop2: ...
  ...
}

For right now my work around is to do a .map that filters out entries without keys, but I'd rather not have to manually dedup the response on each update when the database doesn't contain any duplicates.

@ansorensen Can you create a plnkr for debugging?

I'm running into the same issue, duplicating the first value, until I do a page refresh, and then it disappears.

@Bengejd @ansorensen I don't mind looking into these types of issues. In fact, if there is a bug, I would like to fix it, lest it affect me. However, to do that, this needs to be reproduced in a plunk (or in a failing unit test).

@cartant I appreciate it, but I honestly have no idea how to reproduce it outside of my environment. I'm experiencing a lot of weird stuff with Observables right now, so I'm gonna try and figure out the cause of my problem. If I can't, i'll figure out how to set up the plunk like you asked.

Just one more "me too" - I'm currently having the issue where I get back a duplicate of the first node. Specifically I'm using the AngularFIreDatabase's ".list('my/path')" method.

In addition and hopefully related, I randomly get duplicates all over a list of objects then when I refresh the duplicates disappear and it's incredibly difficult to debug because it happens seemingly at random and I can't reliably reproduce the issue.

I am having the same problem! When I first load the app, the table displays the info as in the db, but if I update or insert a new value, for some reason, if I console the items, i get the 5 items on the db, but the first one is adding up the last query, so for example, if i have two records on the db and they have a value of 100, and 200 the first time it displays 100 and 200, but when I update for instance the second one to 100, the console displays the first item with 300 and the second with 100, so they add up 400. If i cancel or refresh the page, or run the query again by clicking the button it displays the correct info, but when doing it on real time it has that problem

getVentasByRangoFecha(desde: string, hasta: string) {
    let ventas: FirebaseListObservable<Venta[]> = null;

    ventas = <FirebaseListObservable<Venta[]>> this.db.list('ventas', {
      query: {
        orderByChild: 'fecha',
        startAt: desde,
        endAt: hasta
      }
    }).map(items => {
      console.log(items);

      const map = new Map();

      for (const item of items) {
        if (!map.has(item.uid)) {
          map.set(item.uid, item);
        } else {
          const venta: Venta = map.get(item.uid);

          venta.kilos = venta.kilos + item.kilos;
          venta.pesos = venta.pesos + item.pesos;
          venta.ventas = venta.ventas + item.ventas;
          venta.visitas = venta.visitas + item.visitas;
        }
      }

      return Array.from(map.values());
    });

    return ventas;
  }

Thanks

I am also having the same problem. It appears to happen when data is changed as a part of the initial subscription. Very annoying -- I'd have to hack away a solution so I can release my app, but I'd prefer not to. Please fix!

I stopped having this issue shortly after I submitted my comment, this is my current setup of how I'm subscribing to the data, but I doubt it would be any help to you guys. It's typically bad practice to subscribe in the constructor, but oh well, you can do it there, or in the onInit().

constructor(public af: AngularFire, public auth: AuthService, public fbs: FirebaseService) {
    this.buildingCards = this.af.database.list(this.fbs.bLocation);
    this.buildingCards.subscribe();
  }

@davideast There is a serious data duplication problem with database lists. Updates to the list produce duplicates, initial loading of lists produce duplicates, .map and .subscribe produce duplicates. Have you taken a look at this all? This is incredibly frustrating. It seemingly happens at random all over my app.

@davideast Is there any word on this? I'm having this problem as well.

@zaki-arain If you're running into this issue then please create a plnkr replicating it and then open a new issue. We'll be happy to debug :)

Note: The above goes for anyone who runs into this issue.

Was this page helpful?
0 / 5 - 0 ratings