-
-
Notifications
You must be signed in to change notification settings - Fork 205
fix(template): remove scroll
listener
#1563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
☁️ Nx Cloud ReportAttention: This version of the Nx Cloud GitHub bot will cease to function on July 1st, 2023. An organization admin can update your integration here. CI is running/has finished running commands for commit a085ff3. As they complete they will appear below. Click to see the status, the terminal output, and the build insights. 📂 See all runs for this branch ✅ Successfully ran 6 targetsSent with 💌 from NxCloud. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I don't observe any leak on my side, removing the listener without passing the option again works well.
Following the removeEventListener doc, it would fail if another option would be passed like { capture: true }
but passing nothing means "match any event with this name and this handler".
The spec defines the following:
Browser engines store event listeners differently on elements, but usually by "joining" listeners and options. Given the following example: @Component({
selector: 'app-root',
template: `
<section
id="scroll"
#scrollContainer
style="height: 300px; width: 300px; overflow-y: scroll"
>
<p *ngFor="let _ of array">Hey</p>
</section>
<button (click)="destroy()"></button>
`,
})
export class AppComponent {
array = Array.from({ length: 40 });
@ViewChild('scrollContainer', { static: true })
scrollContainer!: ElementRef<HTMLElement>;
private l = () => {
console.log(this);
};
constructor(private ngModuleRef: NgModuleRef<unknown>) {}
ngOnInit(): void {
// @ts-ignore
this.scrollContainer.nativeElement['__zone_symbol__addEventListener'](
'scroll',
this.l,
{ passive: true }
);
}
ngOnDestroy() {
// @ts-ignore
this.scrollContainer.nativeElement['__zone_symbol__removeEventListener'](
'scroll',
this.l
);
}
destroy() {
this.ngModuleRef.destroy();
}
} If we click the 'destroy' button and save the snapshot in Firefox, we may see the folllowing: Now let's find what captures section with its virtual address
Which means the event listener still captures the element. Chrome's Blink also expects the reference to be passed when calling bool EventTarget::removeEventListener(const AtomicString &event_type,
const EventListener *listener,
EventListenerOptions *options) {
return RemoveEventListenerInternal(event_type, listener, options);
} Firefox Gecko is also checking both listener and options passed: if (listener->mListener == aListenerHolder &&
listener->mFlags.EqualsForRemoval(aFlags)) {
mListeners.RemoveElementAt(i);
} One of the engines that's doesn't care about options is Safari's WebKit: bool EventTarget::removeEventListener(const AtomicString& eventType, EventListener* listener, bool useCapture) Since it doesn't have a signature where options should be passed when calling Material is following the same approach and passes options back when removing event listeners: https://github.com/angular/components/blob/285f46dc2b4c5b127d356cb7c4714b221f03ce50/src/material/legacy-slider/slider.ts#L572-L573 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's merge it, it can only be beneficial. Thank you @arturovt.
Codecov Report
@@ Coverage Diff @@
## main #1563 +/- ##
==========================================
- Coverage 81.63% 81.58% -0.05%
==========================================
Files 105 104 -1
Lines 2232 2227 -5
Branches 407 406 -1
==========================================
- Hits 1822 1817 -5
Misses 332 332
Partials 78 78
Flags with carried forward coverage won't be shown. Click here to find out more. |
This requires the same object to be passed back to
removeEventListener
after callingaddEventListener
(otherwise it's a leak).