dynamic-component-loader.service.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import {
  2. ComponentFactory,
  3. Inject,
  4. Injectable,
  5. Injector,
  6. NgModuleFactory,
  7. NgModuleFactoryLoader,
  8. Compiler
  9. } from '@angular/core';
  10. import { from, Observable, throwError, of, } from 'rxjs';
  11. import { mergeMap } from 'rxjs/operators';
  12. import {
  13. DynamicComponentManifest,
  14. DYNAMIC_COMPONENT,
  15. DYNAMIC_COMPONENT_MANIFESTS,
  16. DYNAMIC_MODULE
  17. } from './dynamic-component-manifest';
  18. @Injectable()
  19. export class DynamicComponentLoader {
  20. constructor(
  21. @Inject(DYNAMIC_COMPONENT_MANIFESTS)
  22. private manifests: DynamicComponentManifest[],
  23. private loader: NgModuleFactoryLoader,
  24. private injector: Injector,
  25. private compiler: Compiler
  26. ) {}
  27. /**
  28. * Get the value as an observable
  29. *
  30. * @template T
  31. * @param {(T | NgModuleFactory<T> | Promise<T> | Observable<T>)} value
  32. * @returns
  33. * @memberof LibConfigService
  34. */
  35. private _wrapIntoObservable<T>(value: T | NgModuleFactory<T> | Promise<T> | Observable<T>) {
  36. if (value instanceof Observable) {
  37. return value;
  38. } else if (value instanceof Promise) {
  39. return from(value);
  40. } else {
  41. return of(value);
  42. }
  43. }
  44. /**
  45. * Retrieve a ComponentFactory, based on the specified componentId
  46. * (defined in the DynamicComponentManifest array).
  47. *
  48. * @template T
  49. * @param {string} componentId
  50. * @param {Injector} [injector]
  51. * @returns {Observable<ComponentFactory<T>>}
  52. * @memberof DynamicComponentLoader
  53. */
  54. getComponentFactory<T>(
  55. componentId: string,
  56. injector?: Injector
  57. ): Observable<ComponentFactory<T>> {
  58. const manifest = this.manifests.find(m => m.componentId === componentId);
  59. if (!manifest) {
  60. return throwError(
  61. `DynamicComponentLoader: Unknown componentId "${componentId}"`
  62. );
  63. }
  64. const path = manifest.loadChildren;
  65. if (!path) {
  66. throw new Error(`${componentId} unknown!`);
  67. }
  68. // Check the path type
  69. if (path instanceof Function) {
  70. return this._wrapIntoObservable(path()).pipe(mergeMap((t: any) => {
  71. let moduleFactory = null;
  72. const offlineMode = this.compiler instanceof Compiler;
  73. // true means AOT enalbed compiler (Prod build), false means JIT enabled compiler (Dev build)
  74. moduleFactory = offlineMode ? t : this.compiler.compileModuleSync(t);
  75. return this.loadFactory<T>(moduleFactory, componentId, injector);
  76. }));
  77. } else {
  78. return from(this.load<T>(path, componentId, injector));
  79. }
  80. }
  81. /**
  82. * Get the instance of the component factory
  83. *
  84. * @template T
  85. * @param {string} path
  86. * @param {string} componentId
  87. * @param {Injector} [injector]
  88. * @returns {Promise<ComponentFactory<T>>}
  89. * @memberof DynamicComponentLoader
  90. */
  91. async load<T>(
  92. path: string,
  93. componentId: string,
  94. injector?: Injector
  95. ): Promise<ComponentFactory<T>> {
  96. const ngModuleFactory = await this.loader.load(path);
  97. return await this.loadFactory<T>(ngModuleFactory, componentId, injector);
  98. }
  99. /**
  100. * Load the factory object
  101. *
  102. * @template T
  103. * @param {NgModuleFactory<any>} ngModuleFactory
  104. * @param {string} componentId
  105. * @param {Injector} [injector]
  106. * @returns {Promise<ComponentFactory<T>>}
  107. * @memberof DynamicComponentLoader
  108. */
  109. loadFactory<T>(
  110. ngModuleFactory: NgModuleFactory<any>,
  111. componentId: string,
  112. injector?: Injector
  113. ): Promise<ComponentFactory<T>> {
  114. const moduleRef = ngModuleFactory.create(injector || this.injector);
  115. const dynamicComponentType = moduleRef.injector.get(
  116. DYNAMIC_COMPONENT,
  117. null
  118. );
  119. if (!dynamicComponentType) {
  120. const dynamicModule: DynamicComponentManifest = moduleRef.injector.get(
  121. DYNAMIC_MODULE,
  122. null
  123. );
  124. if (!dynamicModule) {
  125. throw new Error(
  126. 'DynamicComponentLoader: Dynamic module for' +
  127. ` componentId "${componentId}" does not contain` +
  128. ' DYNAMIC_COMPONENT or DYNAMIC_MODULE as a provider.'
  129. );
  130. }
  131. if (dynamicModule.componentId !== componentId) {
  132. throw new Error(
  133. 'DynamicComponentLoader: Dynamic module for' +
  134. `${componentId} does not match manifest.`
  135. );
  136. }
  137. const path = dynamicModule.loadChildren;
  138. if (!path) {
  139. throw new Error(`${componentId} unknown!`);
  140. }
  141. if (path instanceof Function) {
  142. return this._wrapIntoObservable(path()).pipe(mergeMap((t: any) => {
  143. let moduleFactory = null;
  144. const offlineMode = this.compiler instanceof Compiler;
  145. // true means AOT enalbed compiler (Prod build), false means JIT enabled compiler (Dev build)
  146. moduleFactory = offlineMode ? t : this.compiler.compileModuleSync(t);
  147. return this.loadFactory<T>(moduleFactory, componentId, injector);
  148. })).toPromise();
  149. } else {
  150. return this.load<T>(path, componentId, injector);
  151. }
  152. }
  153. return Promise.resolve(
  154. moduleRef.componentFactoryResolver.resolveComponentFactory<T>(
  155. dynamicComponentType
  156. )
  157. );
  158. }
  159. }