valor-software / ngx-bootstrap

Fast and reliable Bootstrap widgets in Angular (supports Ivy engine)
https://valor-software.com/ngx-bootstrap
MIT License
5.53k stars 1.69k forks source link

ngx-bootstrap ComponentLoader have to run destroy() method when component hide? #6499

Closed vkvashul closed 1 month ago

vkvashul commented 2 years ago

Bug description: Open and close typeahead dropdown multiple times lead to memory leaks (slow down browser). Open and close multiple times dropdown -slow down hovering over items (looks like they still exist in browser memory) Same effect exist with other components that use ngx ComponentLoader

How to solve this issue in true way?

Plunker/StackBlitz that reproduces the issue: StackBlitz: https://stackblitz.com/edit/angular-ivy-ez8g8e?file=src%2Fapp%2Fapp.module.ts

I try to provide same ComponentLoader, but with running method destroy() if component hide -

  1. this._componentRef.destroy();

sample of first open first-open

sample after 10 times open after-10-times-open

sherlock1982 commented 2 years ago

I was just looking at #5999, might be related because it also leaks in ComponentLoader. Proposed patch works

rehdie commented 1 year ago

For dropdowns I've implemented a hacky workaround to fix this issuee, until the PR of @sherlock1982 is merged. Maybe it's useful for some of you:

@Directive({
    selector: '[dropdown]'
})
export class BootstrapDisposeDropdownDirective implements OnDestroy {

    private destroy$ = new Subject<void>();
    private componentRef: ComponentRef<unknown>;

    constructor(@Optional() private dropdown: BsDropdownDirective) {
        dropdown.onShown.pipe(takeUntil(this.destroy$)).subscribe(() => this.saveComponentRef());
        dropdown.onHidden.pipe(takeUntil(this.destroy$)).subscribe(() => this.destroyComponent());
    }

    ngOnDestroy(): void {
        this.destroy$.next();
        this.destroy$.complete();
        this.destroyComponent();
    }

    private saveComponentRef() {
        const componentLoader: ComponentLoader<any> = this.dropdown['_dropdown'];
        if (componentLoader) {
            this.componentRef = componentLoader['_componentRef'];
        }
    }

    private destroyComponent() {
        this.componentRef?.destroy();
        this.componentRef = null;
    }
}