mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-05 08:01:20 +08:00
fix(angular): correctly destroy angular application between each render
Avoids a lot of memory leakage 😵
This commit is contained in:
parent
3a9ef82903
commit
890cb09c52
@ -19,7 +19,7 @@ import { RendererService } from './RendererService';
|
||||
* Bootstrap angular application to generate a web component with angular element
|
||||
*/
|
||||
export class ElementRendererService {
|
||||
private platform = RendererService.getInstance().platform;
|
||||
private rendererService = RendererService.getInstance();
|
||||
|
||||
/**
|
||||
* Returns a custom element generated by Angular elements
|
||||
@ -36,7 +36,8 @@ export class ElementRendererService {
|
||||
new BehaviorSubject<ICollection>(storyFnAngular.props)
|
||||
);
|
||||
|
||||
return this.platform
|
||||
return this.rendererService
|
||||
.newPlatformBrowserDynamic()
|
||||
.bootstrapModule(createElementsModule(ngModule))
|
||||
.then((m) => m.instance.ngEl);
|
||||
}
|
||||
|
@ -111,5 +111,33 @@ describe('RendererService', () => {
|
||||
expect(document.body.getElementsByTagName('storybook-wrapper')[0].innerHTML).toBe('🍺');
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly destroy angular platform between each render', async () => {
|
||||
let countDestroy = 0;
|
||||
|
||||
await rendererService.render({
|
||||
storyFnAngular: {
|
||||
template: '🦊',
|
||||
props: {},
|
||||
},
|
||||
forced: false,
|
||||
parameters: {} as any,
|
||||
});
|
||||
|
||||
rendererService.platform.onDestroy(() => {
|
||||
countDestroy += 1;
|
||||
});
|
||||
|
||||
await rendererService.render({
|
||||
storyFnAngular: {
|
||||
template: '🐻',
|
||||
props: {},
|
||||
},
|
||||
forced: false,
|
||||
parameters: {} as any,
|
||||
});
|
||||
|
||||
expect(countDestroy).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -35,15 +35,13 @@ export class RendererService {
|
||||
constructor() {
|
||||
if (typeof NODE_ENV === 'string' && NODE_ENV !== 'development') {
|
||||
try {
|
||||
// platform should be set after enableProdMode()
|
||||
enableProdMode();
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(e);
|
||||
}
|
||||
}
|
||||
// platform should be set after enableProdMode()
|
||||
this.platform = platformBrowserDynamic();
|
||||
this.initAngularBootstrapElement();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,15 +74,32 @@ export class RendererService {
|
||||
}
|
||||
this.storyProps$ = new BehaviorSubject<ICollection>(storyFnAngular.props);
|
||||
|
||||
this.initAngularBootstrapElement();
|
||||
await this.platform.bootstrapModule(
|
||||
await this.newPlatformBrowserDynamic().bootstrapModule(
|
||||
createStorybookModule(
|
||||
getStorybookModuleMetadata({ storyFnAngular, parameters }, this.storyProps$)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private initAngularBootstrapElement() {
|
||||
public newPlatformBrowserDynamic() {
|
||||
// Before creating a new platform, we destroy the previous one cleanly.
|
||||
this.destroyPlatformBrowserDynamic();
|
||||
|
||||
this.initAngularRootElement();
|
||||
this.platform = platformBrowserDynamic();
|
||||
|
||||
return this.platform;
|
||||
}
|
||||
|
||||
public destroyPlatformBrowserDynamic() {
|
||||
if (this.platform && !this.platform.destroyed) {
|
||||
// Destroys the current Angular platform and all Angular applications on the page.
|
||||
// So call each angular ngOnDestroy and avoid memory leaks
|
||||
this.platform.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private initAngularRootElement() {
|
||||
// Adds DOM element that angular will use as bootstrap component
|
||||
const storybookWrapperElement = document.createElement(
|
||||
RendererService.SELECTOR_STORYBOOK_WRAPPER
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Story, Meta } from '@storybook/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'on-destroy',
|
||||
template: `Current time: {{ time }} <br />
|
||||
📝 The current time in console should no longer display after a change of stroy`,
|
||||
})
|
||||
class OnDestroyComponent implements OnInit, OnDestroy {
|
||||
time: string;
|
||||
|
||||
interval: any;
|
||||
|
||||
ngOnInit(): void {
|
||||
const myTimer = () => {
|
||||
const d = new Date();
|
||||
this.time = d.toLocaleTimeString();
|
||||
console.info(`Current time: ${this.time}`);
|
||||
};
|
||||
|
||||
myTimer();
|
||||
this.interval = setInterval(myTimer, 3000);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Basics / Component / with ngOnDestroy',
|
||||
component: OnDestroyComponent,
|
||||
parameters: {
|
||||
storyshots: { disable: true }, // disabled due to new Date()
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
export const SimpleComponent: Story = () => ({
|
||||
component: OnDestroyComponent,
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user