ngneat / from-event

🦊 ViewChild and FromEvent β€” a Match Made in Angular Heaven

Home Page:https://netbasal.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support dynamic views

ribizli opened this issue Β· comments

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

@ViewChild's setter is called several times, if the element is toggled in the view. If the element is removed (e.g. through *ngIf=show), the setter is called with undefined ElementRef. This case is not handled (NPE).

If we swap the element with an other (e.g. ngIf + else with same #templateVar), the setter is called again with the other ElementRef. (1) The former fromEvent is not unsubscribed (might not cause memory leak), and (2) the chaining also breaks, since fromEvent is not usable as event target (aka in subscribe).

Expected behavior

To keep the original Subject for the users and swap out it's source with the latest ElementRef's events.

Also verify if unsubscribe from the previous ElementRef's events is needed.

Minimal reproduction of the problem with instructions

https://ng-run.com/edit/VqWigxZwytu3Sj26PDnK

  1. click on 'test' and see the alert
  2. click on 'toggle' to swap out the button first
  3. click on 'test alternative' and see that alert is not triggered anymore
  4. click on 'toggle' to remove the button, see the NPE

What is the motivation / use case for changing the behavior?

Free this lib from bugs ;)

Environment


Angular version: 9.1.1


Browser:
- [X] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 

@tonivj5 could you take it, please?

@tonivj5 if you want I could take over, but you must feel more comfortable with your change.

Good catch @ribizli

I'll fix it πŸ‘

We have an issue:

error TS1169: A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.

At build?

Yes, I added the build to the CI process.

Ok, I will check it πŸ‘

@tonivj5 I'm not sure, about the code in finalize(), where the subject gets completed (event$.complete()). Can you please explain it for me? Doesn't it cause late subscribers won't get notified anymore?

@tonivj5 also (IMHO) using a Subject as source is not really safe after operations piped on it... (I've got TS complaining about)

See my plain test for my .complete() concern: https://stackblitz.com/edit/rxjs-dlybi3?file=index.ts

Can you please explain it for me?

Sure!

Doesn't it cause late subscribers won't get notified anymore?

No, it will create a new Subject and subscription to the event with the new subscription. It works because we return a defer observable, on subscribe, it will create the Subject (if it's needed) and subscribe to the event (if it's possible). Defer docs.

Defer allow us to run initIfNeeded and subscribeToEventIfPossible on every subscription. And finalize release the memory (complete the the subject and unsubscribe from fromEvent) when no one is subscribed to it.

also (IMHO) using a Subject as source is not really safe after operations piped on it... (I've got TS complaining about)

However, type says that pipe returns an Observable, it's returning itself (a Subject), that's the reason because we cast it.

Yeah, we could have an source symbol (subject piped) and our subject symbol (without piped), or in our defer returns this[tokens.subject].asObservable() to limit how it can be used. But I think it doesn't improve it too much.

See my plain test for my .complete() concern: stackblitz.com/edit/rxjs-dlybi3?file=index.ts

This example doesn't show the same behavior that we are really doing. Here I have modified your example to reflect more properly how FromEvent is working: https://stackblitz.com/edit/rxjs-eacmif?file=index.ts

Surely it can be improved! You can play with the code and check if tests pass (I think it covers a lot of use-cases πŸ˜…)

A few notes if we remove defer or share (it could lead to more problems...):

  • Without defer, the subject or subscription wouldn't be created on demand, for example, this POC would fail
constructor() {
	const logSomething$ = this.button$/* getter */.pipe(tap(console.log));
	// called finalize (subject removed and unsubscribe from event)
	logSomething$.subscribe().unsubscribe();

	// point to a completed/dead subject, it completes automatically
	logSomething/* no getter */.subscribe()
}
  • Without share, if one subject is unsubscribed, finally is called and clean up is done and this would affect to other subscriptions to the same subject, for example, this POC would fail
constructor() {
	this.button$.subscribe();
	// it calls finalize and this subscription and the above one complete
	this.button$.subscribe().unsubscribe();
}

@tonivj5 You did a great job!