TNG / ngqp

Declaratively synchronize form controls with the URL
https://tng.github.io/ngqp
MIT License
81 stars 8 forks source link

No chance to access first value of paramGroup on component load #176

Closed steinsag closed 3 years ago

steinsag commented 3 years ago

StackBlitz: https://stackblitz.com/github/steinsag/angular-ngqp?file=src%2Fapp%2Ffilter-list.component.ts Belonging GitHub repo: https://github.com/steinsag/angular-ngqp

Steps to reproduce:

Sub-sequent clicks on the buttons or manually entering a different value in quickSearch filter works as expected. If one returns to "home" component, the problem can be reproduced again.

In issue https://github.com/TNG/ngqp/issues/72, a work-around is described to access the first value of a param group when a component is loaded. Idea is to wrap paramGroup.valueChanges with another observable with a startWith value using something like:

this.paramGroupValues$ = this.paramGroup.valueChanges.pipe(
  startWith(this.paramGroup.value),
  takeUntil(this.ngUnsubscribe)
);

However, this doesn't seem to work, because this.paramGroup.value always returns null on component init.

Airblader commented 3 years ago

Thanks for reporting this and, importantly, providing a StackBlitz! I will take a closer look and a more detailed reply later on, but if you need a way to make this work quickly, this is it:

    this.paramGroupValues$ = defer(() => this.paramGroup.valueChanges.pipe(
      startWith(this.paramGroup.value),
      takeUntil(this.ngUnsubscribe)
    ));
steinsag commented 3 years ago

I can confirm this work-around resolves the issue!

Airblader commented 3 years ago

So to explain a bit further, in case it isn't clear now anyway, the difference is essentially the timing of when paramGroup.value is evaluated. In

this.paramGroupValues$ = this.paramGroup.valueChanges.pipe(
  startWith(this.paramGroup.value),
  takeUntil(this.ngUnsubscribe)
);

this happens when the observable is created, not when it is subscribed. At this point ngqp has had no time to synchronize the value from the URL, of course, and thus the value is null. Wrapping this into defer we can make this evaluation "lazy" such that it occurs when the observable is subscribed, which is what we want: upon subscription we track changes, but start once initially with the current value. This is essentially the same as if valueChanges was a ReplaySubject(1) instead of a Subject.

The fact that I posted this wrong in #72 was just an oversight, thanks for correcting it there. :-) I'll also update my comment there afterwards.

All that being said, this does still behave as intended, and I still think changing the semantic of valueChanges isn't ideal and would rather lean towards the idea of adding a value$ observable which is replayed. I've opened #177 for that (and will in turn close this issue).