angular / angularfire

Angular + Firebase = ❤️
https://firebaseopensource.com/projects/angular/angularfire2
MIT License
7.64k stars 2.2k forks source link

limitToLast and 'child_added' do not work together #1773

Closed Maistho closed 3 years ago

Maistho commented 6 years ago

Version info

Angular: 6.0.7

Firebase: 5.2.0

AngularFire: 5.0.0-rc.11

How to reproduce these conditions

Failing test unit, Plunkr, or JSFiddle demonstrating the problem

See this stackblitz

https://stackblitz.com/edit/angular-nm6r3a?file=app%2Fapp.component.ts

Steps to set up and reproduce

this.list$ = this.db.list(ref, ref => ref.limitToLast(1)).valueChanges(['child_added']);

When using limitToLast and 'child_added' the limit is not kept after the first item.

Expected behavior

I should only get the latest added child

Actual behavior

I get every child that is added since I first started the subscription

ValentinFunk commented 6 years ago

i think you're looking for

this.list$ = this.db.list(ref).valueChanges(['child_added']).pipe(first());

Maistho commented 6 years ago

@Kamshak Yes, that's what I'm using, but the bug is that limitToLast should already limit to only the most recently added item.

The following code will work, but is a workaround that I shouldn't need.

this.list$ = this.db.list(ref, ref => ref.limitToLast(1))
  .valueChanges(['child_added'])
  .pipe(first());
ValentinFunk commented 6 years ago

Limit to last will give you the last highest priority item (unless you order by child or something else) i think. It does not mean give me only last added child (1 value). See also https://firebase.google.com/docs/database/admin/retrieve-data#limit-queries

var ref = db.ref("dinosaurs");
ref.orderByChild("weight").limitToLast(2).on("child_added", function(snapshot) {
  console.log(snapshot.key);
});

See the explanation:

The child_added callback is triggered exactly two times, unless there are less than two dinosaurs stored in the database. It will also get fired for every new, heavier dinosaur that gets added to the database

In your example the most recently added item changes once you add a new item, that's why it will emit a new one. Consider an app where you want to show the last user that signed up, you'd expect it to react if a new user is added.

Maistho commented 6 years ago

Thanks for a good explanation! 👍

I still think that the behaviour is somewhat confusing though, since I get only the n most recent values when not only listening to child_added.

Maybe the Observable should not emit a list, and only emit values instead? I feel like my confusion is from getting a list with a bunch of child_added events instead of getting each child one by one. When I've gotten the first child_added event I will get that same event in the array every time a new child is added. 🤔

What I'm after in the end is to have an Observable that emits any new children in the list, and that only gives me the most recent child when starting the subscription. Probably easier to just not use angularfire in that case:

const subject = new ReplaySubject<any>(1)
const ref = this.db.database.ref('/dinosaurs');
ref.limitToLast(1).on('child_added', snapshot => {
  this.ngZone.run(() => {
    subject.next(snapshot.val());
  });
});
ValentinFunk commented 6 years ago

Yeah you're right that might be a bug, it seems that in the implementation of the list observable it always merges in a "value" change [1], even though "child_added" would be enough to fetch everything. Perhaps there is a reason for this, @davideast probably knows more.

[1] See Line 10 here: https://github.com/angular/angularfire2/blob/2c2fe021667b8675a6ceabb891f979c20d632570/src/database/list/changes.ts#L9-L16

davideast commented 5 years ago

@Kamshak Child added is not enough to fetch everything. We need to know what the "initial state" of the list. That way we only emit the array once the "initial state" is in place.

jamesdaniels commented 3 years ago

closing as it seems this is working as intended for right now