Kolby
Kolby

Reputation: 2865

Angular passing dynamic entry components into module and then passing them again into another module

I'm making a modal component for a component library. I made a 3rd party modal library that I'm using within my component library. A main feature is being able to pass a component via a service and dynamically adding it to the modal.

My modal lib has a static method that allows you to add your component to the module's entry components. It looks like:

export class A11yModalModule {
  static withComponents(components: any[]) {
    return {
      ngModule: A11yModalModule,
      providers: [{
        provide: ANALYZE_FOR_ENTRY_COMPONENTS,
        useValue: components,
        multi: true
      }]
    };
  }
}

Cool, that works. I can pass components into it when I import the module like this: A11yModalModule.withComponents([ModalContentComponent])

My problem occurs when I abstract this out another level. So now instead of 2 modules I have 3. I need to pass a component like I did above from the lib consumer's module, to my component module, and then into the modal module.

How can I pass components from the lib module to the modal module?


I think I'm getting close. Here are my 3 modules

// app module
@NgModule({
  declarations: [AppComponent, ModalContentComponent],
  imports: [
    LibModalModule.withComponents([ModalContentComponent])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }



// lib module
@NgModule({
  imports: [CommonModule],
  declarations: [LibModal],
  providers: [LibModalService],
  exports: []
})
export class LibModalModule {
  static withComponents(components: any[]) {
    return {
      ngModule: LibModalModule,
      imports: [CommonModule, A11yModalModule.withComponents(components)]
    };
  }
}



// a11y modal module
@NgModule({
  imports: [CommonModule],
  declarations: [ModalComponent],
  exports: [],
  providers: [ModalService, DomService],
  entryComponents: [ModalComponent]
})
export class A11yModalModule {
  static withComponents(components: any[]) {
    return {
      ngModule: A11yModalModule,
      providers: [{
        provide: ANALYZE_FOR_ENTRY_COMPONENTS,
        useValue: components,
        multi: true
      }]
    };
  }
}

Upvotes: 7

Views: 5408

Answers (2)

yurzui
yurzui

Reputation: 214017

withComponents method should return ModuleWithProviders object which is just wrapper around a module that also includes the providers.

It can't have imports property or something else because it doesn't understand those properties. Here's an excerpt from angular source code that is responsible from reading metadata from ModuleWithProviders:

else if (importedType && importedType.ngModule) {
  const moduleWithProviders: ModuleWithProviders = importedType;
  importedModuleType = moduleWithProviders.ngModule;
  if (moduleWithProviders.providers) {
    providers.push(...this._getProvidersMetadata(
        moduleWithProviders.providers, entryComponents,
        `provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
            importedType));
    }
}

As we can see angular compiler takes providers from the object that will returned in withComponents method.

So, in order to merge your modules you can either use your approach(provide ANALYZE_FOR_ENTRY_COMPONENTS in LibModalModule.withProviders) or reuse A11yModalModule.withComponents like:

@NgModule({
  imports: [CommonModule, A11yModalModule],
  providers: [LibModalService],
  exports: []
})
export class LibModalModule {
  static withComponents(components: any[]) {
    return {
      ngModule: LibModalModule,
      providers: A11yModalModule.withComponents(components).providers
    };
  }
}

(Tested with AOT)

Also A11yModalModule has to be imported in LibModalModule if we want its providers to be included in our root module injector (And i suppose you're going to use ModalService and DomService that are declated in A11yModalModule). The reason of this is that angular includes all providers from transitive module in root module injector.

See also:

Upvotes: 9

Kolby
Kolby

Reputation: 2865

I had a bug that was giving me a false flag. Turns out you can just add the same withComponents method to the component library module and it passes the component through. I'd love an explanation on how this works if anyone knows.

// app module
@NgModule({
  declarations: [AppComponent, ModalContentComponent],
  imports: [
    LibModalModule.withComponents([ModalContentComponent])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }



// lib module
@NgModule({
  imports: [CommonModule, A11yModalModule],
  declarations: [LibModal],
  providers: [LibModalService],
  exports: []
})
export class LibModalModule {
  static withComponents(components: any[]) {
    return {
      ngModule: LibModalModule,
      providers: [{
        provide: ANALYZE_FOR_ENTRY_COMPONENTS,
        useValue: components,
        multi: true
      }]
    };
  }
}



// a11y modal module
@NgModule({
  imports: [CommonModule],
  declarations: [ModalComponent],
  exports: [],
  providers: [ModalService, DomService],
  entryComponents: [ModalComponent]
})
export class A11yModalModule {
  static withComponents(components: any[]) {
    return {
      ngModule: A11yModalModule,
      providers: [{
        provide: ANALYZE_FOR_ENTRY_COMPONENTS,
        useValue: components,
        multi: true
      }]
    };
  }
}

Upvotes: 0

Related Questions