【问题标题】:Angular Dependency injection issues in angular libraries角度库中的角度依赖注入问题
【发布时间】:2022-03-09 20:59:22
【问题描述】:

我有一个 Angular 应用程序,它有 3 个库。一个是共享库,另外两个是用作微应用程序的库,我使用角度元素来渲染这两个库。共享库具有注入 HttpClient、DecimalPipe 等 Angular 类的服务。我收到静态空注入器错误 (NullInjectorError: R3InjectorError[CounterService -> CounterService -> CounterService -> CounterService -> DecimalPipe]) 共享库模块在导入部分有 HttpClientModule、Common 模块、浏览器模块。如果我在共享库模块中创建另一个服务,它可以与 DI 一起正常工作。

@NgModule({
  declarations: [SharedLibComponent],
  imports: [
    BrowserModule,
    CommonModule,
    HttpClientModule
  ],
  exports: [SharedLibComponent],
  providers: [HttpClient, DecimalPipe]
})
export class SharedLibModule { }


@Injectable({
  providedIn: 'platform'
})

export class CounterService {

  count: number = 0;

  constructor(private _decimalPipe: DecimalPipe) {
    console.log('inside constructor');
  }

  increment() {
    this.count = this.count + 1;
  }

  decrement() {
    this.count = this.count - 1;
  }
}

这里我选择providedIn 作为平台,因为它是微应用。我只需要一个跨多个微应用的组件实例,我可以通过将微应用创建为带有 shell 项目的库来实现。

@NgModule({
  declarations: [
    MyLibComponent,
    DashboardComponent,
    MyAccountComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  imports: [
    BrowserModule,
    MyLibRoutingModule,
    HttpClientModule,
    SharedLibModule
  ]
})
export class MyLibModule implements DoBootstrap {

  constructor(private injector: Injector) {
    console.log('inside my lib module constructor');
  }

  ngDoBootstrap() {
    console.log("PMB Module: Entered ngDoBootstrap...");
    const myElementExists = !!customElements.get("pmb-lib");
    if (!myElementExists) {
      const appElement = createCustomElement(MyLibComponent, {
        injector: this.injector
      });
      customElements.define("my-lib", appElement);
    }
    console.log("My lib Module: Completed ngDoBootstrap...");
  }
}

// If there is already a platform, reuse it, otherwise create a new one
(getPlatform() || platformBrowser()).bootstrapModule(MyLibModule).catch(err => console.log(err));

非常感谢任何想法或帮助。

【问题讨论】:

    标签: angular


    【解决方案1】:

    我很高兴看到人们实际使用my POC。 :)

    B2T(也许这有助于将错误推理可视化)

    当您在平台级别提供服务时,
    它们继承自平台 Injector

    当你引导你的模块时,它会导入例如HttpClientModuleCommonModule 等,
    然后这些模块继承自平台 Injector

    那么这里的问题是什么?对,为了能够从 HttpClientModule/CommonModule 注入东西,服务必须从该级别继承注入器,不是平台级别

    我们可以创建我们的自定义“平台模块”,其中注入器已经知道 HttpClientModule/CommonModule 并使用该“平台模块”来“引导”其他微前端。但是,即使在这里,您的服务也必须是“root”或更少,而不是“平台”

    示例:

    /**
     * This is a service which is provided by the "platform module"
     * so it can be used by all other modules. SINGLETON
     */
    @Injectable()
    export class GlobalService {
        private static count = 0;
    
        constructor(readonly httpClient: HttpClient) {
            GlobalService.count++;
    
            // Ctor would crash anyway. But hey, insanity..
            if (!httpClient) {
                throw new Error('HTTPCLIENT COULD NOT BE INJECTED');
            }
        }
    
        printMessage() {
            console.log(`[${GlobalService.name}]: Count: ${GlobalService.count}`);
        }
    }
    
    /**
     * This is your "platform module", which provides services
     * that can be used by every other micro frontends.
     *
     * DO NOT IMPORT THIS MODULE
     *
     * the modules have access to everything provided by this module.
     * This is the grandparent of your parents grandparents.
     *
     * Services provided here are shared globally,
     * they will be singleton, and they access to stuff
     * like HttpClientModule. Think of it like "ApplicationModule"
     */
    @NgModule({
        imports: [BrowserModule, HttpClientModule],
        providers: [GlobalService]
    })
    export class MfePlatformModule implements DoBootstrap {
        constructor(@Optional() @SkipSelf() @Inject(MfePlatformModule) parentModule: MfePlatformModule|null) {
            if (parentModule) {
                throw new Error(
                    `You did it right? You imported MfePlatformModule...`);
            }
        }
    
        // This is needed, if there are no components to bootstrap
        ngDoBootstrap(): void { }
    }
    
    /**
     * This is an example module, that makes use of the
     * service which is provided at platform level
     * Everything inside this module can also inject.
     * CAUTION
     * IMPORTANT
     * ATTENTION
     * The modules NEED to have an id like here
     * Can be any string, as long as they are unique
     */
    @NgModule({
        id: ExampleMfeModule.name
    })
    export class ExampleMfeModule {
        private static count = 0;
    
        constructor(readonly sharedService: GlobalService) {
            ExampleMfeModule.count++;
            console.log(`ExampleMfeModule #${ExampleMfeModule.count} successfully instantiated`);
            this.sharedService.printMessage();
        }
    }
    
    /**
     * Returns the platform module create that contains
     * the root module including the injector,
     * which is used as the parent for every other module
     */
    export const getPlatformModule = (function () {
        // Closure holdung the singleton module ref
        let platformModule: NgModuleRef<MfePlatformModule>;
        return () => {
            return new Promise<NgModuleRef<MfePlatformModule>>(resolve => {
                if (platformModule) {
                    resolve(platformModule);
                }
                platformBrowser().bootstrapModule(MfePlatformModule)
                    .then(moduleRef => {
                        platformModule ||= moduleRef;
                        resolve(platformModule);
                    });
            });
        };
    })();
    
    /**
     * "Bootstrap" your micro frontends using this factory
     * instead of platformBrowser. They will have access
     * to global services (incl. HttpClientModule).
     * 
     * CAUTION: This is not actually a real bootstrap, where the
     * ngDoBootstrap will be called. This is more like importing.
     * If you want to init stuff, do it in the constructor like above
     * 
     * @param ngModule The Micro Frontend NgModule you want to "bootstrap"
     */
    export const bootstrapMfe = <T>(ngModule: Type<T>): Promise<NgModuleRef<T>> =>
        getPlatformModule()
            .then(pf =>
                (getModuleFactory(ngModule.name) as NgModuleFactory<T>).create(pf.injector))
            .catch((error: Error) => console.error(`Error trying to bootstrap mfe ${ngModule}\n${error?.message}`, error) as any);
    
    
    // Lets bootstrap two modules and check if everything works...
    bootstrapMfe(ExampleMfeModule)
        .then(one => console.log(`Boostrapped first mfe`, one.instance))
        .then(() => bootstrapMfe(ExampleMfeModule))
        .then(second => console.log(`Boostrapped second mfe`, second.instance))
        .catch(error => console.log(error));
    

    浏览器中的控制台输出:

    【讨论】:

    • 感谢您的回复。以我目前的设置,我很难做到这一点。我将尝试创建一个 git repo 来分享我当前的设置,以便您提供建议。如果我将此代码移动到我的 shell 项目(不是库),则使用我的设置。如果我将这些导出并让我的库项目访问这些函数,因为它们位于它们自己的源之外,我会遇到构建问题。
    猜你喜欢
    • 2018-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-19
    • 2015-02-14
    相关资源
    最近更新 更多