diff --git a/components/code-editor/code-editor.service.ts b/components/code-editor/code-editor.service.ts index 40d549a4246..520f7179ca4 100644 --- a/components/code-editor/code-editor.service.ts +++ b/components/code-editor/code-editor.service.ts @@ -5,7 +5,7 @@ import { DOCUMENT } from '@angular/common'; import { Inject, Injectable, OnDestroy } from '@angular/core'; -import { BehaviorSubject, Observable, of as observableOf, Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, of, ReplaySubject, Subscription } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { CodeEditorConfig, NzConfigService } from 'ng-zorro-antd/core/config'; @@ -26,14 +26,22 @@ function tryTriggerFunc(fn?: (...args: NzSafeAny[]) => NzSafeAny): (...args: NzS }; } +// Caretaker note: previously, these were `NzCodeEditorService` properties. +// They're kept as static variables because this will allow loading Monaco only once. +// This applies to micro frontend apps with multiple Angular apps or a single Angular app +// that can be bootstrapped and destroyed multiple times (e.g. using Webpack module federation). +// Root providers are re-initialized each time the app is bootstrapped. Platform providers aren't. +// We can't make the `NzCodeEditorService` to be a platform provider (`@Injectable({ providedIn: 'platform' })`) +// since it depends on other root providers. +const loaded$ = new ReplaySubject(1); +let loadingStatus = NzCodeEditorLoadingStatus.UNLOAD; + @Injectable({ providedIn: 'root' }) export class NzCodeEditorService implements OnDestroy { private document: Document; private firstEditorInitialized = false; - private loaded$ = new Subject(); - private loadingStatus = NzCodeEditorLoadingStatus.UNLOAD; private option: JoinedEditorOptions = {}; private config: CodeEditorConfig; private subscription: Subscription | null; @@ -70,12 +78,12 @@ export class NzCodeEditorService implements OnDestroy { } requestToInit(): Observable { - if (this.loadingStatus === NzCodeEditorLoadingStatus.LOADED) { + if (loadingStatus === NzCodeEditorLoadingStatus.LOADED) { this.onInit(); - return observableOf(this.getLatestOption()); + return of(this.getLatestOption()); } - if (this.loadingStatus === NzCodeEditorLoadingStatus.UNLOAD) { + if (loadingStatus === NzCodeEditorLoadingStatus.UNLOAD) { if (this.config.useStaticLoading && typeof monaco === 'undefined') { warn( 'You choose to use static loading but it seems that you forget ' + @@ -87,7 +95,7 @@ export class NzCodeEditorService implements OnDestroy { } } - return this.loaded$.asObservable().pipe( + return loaded$.pipe( tap(() => this.onInit()), map(() => this.getLatestOption()) ); @@ -99,11 +107,11 @@ export class NzCodeEditorService implements OnDestroy { return; } - if (this.loadingStatus === NzCodeEditorLoadingStatus.LOADING) { + if (loadingStatus === NzCodeEditorLoadingStatus.LOADING) { return; } - this.loadingStatus = NzCodeEditorLoadingStatus.LOADING; + loadingStatus = NzCodeEditorLoadingStatus.LOADING; const assetsRoot = this.config.assetsRoot; const vs = assetsRoot ? `${assetsRoot}/vs` : 'assets/vs'; @@ -112,7 +120,9 @@ export class NzCodeEditorService implements OnDestroy { loadScript.type = 'text/javascript'; loadScript.src = `${vs}/loader.js`; - loadScript.onload = () => { + + const onLoad = (): void => { + cleanup(); windowAsAny.require.config({ paths: { vs } }); @@ -120,17 +130,34 @@ export class NzCodeEditorService implements OnDestroy { this.onLoad(); }); }; - loadScript.onerror = () => { + + const onError = (): void => { + cleanup(); throw new Error(`${PREFIX} cannot load assets of monaco editor from source "${vs}".`); }; + const cleanup = (): void => { + // Caretaker note: we have to remove these listeners once the `