Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Migrating conditional boostrap of multiple angular apps with shared services to nested ngModules

Tags:

angular

The problem

I am migrating RC4 to RC5, so far it works but since I have to adjust the bootstrap anyway I was wondering if someone had a cleaner solution for my bootstrap (probably with ngModules) since to me it felt sort of hackish.

Basically it bootstraps multiple components that are not always there depending on whether they are on the page or not and it makes sure configuration/helpers are shared among the components and their children. I could directly wrap the 3 components into ngModules but then I would still have to inject the shared services to the modules. Is there a way to wrap all the 3 components into a single module and bootstrap them only when they are available on the page? I also didnt see an init hook in ngModule where I could preconfigure the services before they get injected into the child components.

Here's the relevant part from my old main.ts:

var cs = new ConfigurationService();
var hs = new HelperService(cs);
var injector = ReflectiveInjector.resolveAndCreate([TranslateService,
    {provide: TranslateLoader, useClass: JsonLoader}, HTTP_PROVIDERS]);
var ts = injector.get(TranslateService);

ts.setDefaultLang('en');
ts.use(cs.getLanguage());

var defaultProviders = [
    {provide: TranslateService, useValue: ts},
    {provide: ConfigurationService, useValue: cs},
    {provide: HelperService, useValue: hs}
];

if ($('notification-widget').length > 0) {
    bootstrap(NotificationWidgetComponent, defaultProviders);
}

if ($('livesearch-widget').length > 0){
    bootstrap(LivesearchWidgetComponent, defaultProviders);
}

if ($('enterprise-search').length > 0){
    bootstrap(EnterpriseSearchComponent, defaultProviders);
}

Partial solution

I found a way to translate the concept:

main.ts is straightforward as described by the docs:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule }              from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

The module itself dosn't use the bootstrap annotation since this cannot be done conditionally, you can instead just use the declarations so angular knows you potential starting points and implemente the bootstrap manually:

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ NotificationWidgetComponent, LivesearchWidgetComponent, EnterpriseSearchComponent ],
    providers: [ConfigurationService, HelperService, TranslateService,
        {provide: TranslateLoader, useClass: JsonLoader}]
})
export class AppModule {
    constructor(
        private conf: ConfigurationService,
        private trans: TranslateService,
        private help: HelperService
    ){
        this.trans.setDefaultLang('en');
        this.trans.use(this.conf.getLanguage());
    }

    ngDoBootstrap(){
        let providers = [
            {provide: ConfigurationService, useValue: this.conf},
            {provide: HelperService, useValue: this.help},
            {provide: TranslateService, useValue: this.trans}
        ];

        if ($('notification-widget').length > 0) {
            bootstrap(NotificationWidgetComponent, providers);
        }

        if ($('livesearch-widget').length > 0){
            bootstrap(LivesearchWidgetComponent, providers);
        }

        if ($('enterprise-search').length > 0){
            bootstrap(EnterpriseSearchComponent, providers);
        }
    }
}

Service configuration can be handled in the module constructor and injection is done manually as before. However the bootstrap method is depricated, so I changed the last part of the ngDoBootstrap to this (just as a test, ignoring the two above):

    if ($('enterprise-search').length > 0){
      platformBrowserDynamic().bootstrapModule(EnterpriseSearchModule, {providers: providers});
    }

The module itself is rather simple:

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ EnterpriseSearchComponent ],
    bootstrap: [ EnterpriseSearchComponent ]
})
export class EnterpriseSearchModule { }

I now have the problem, that the automatically bootstrapped component cannot resolve the configuration service, although it is explicitly given to the wrapping module as a provider. This seems like an angular bug to me or am I simply doing something wrong?

like image 930
Tom Avatar asked Nov 08 '22 10:11

Tom


1 Answers

the ngDoBootstrap lifecycle hook doesn't work like that. what you're expecting is something like ngOnBootstrap or ngAfterBootstrap or ngOnInit none of them work on NgModule though. For ngDoBootstrap what you're looking to do is

ngDoBootstrap(moduleRef) {
  const appRef = moduleRef.injector.get(ApplicationRef);
  // somehow grab compFactory probably from entryComponents
  // bootstrap each entryCoponent
  appRef.bootstrap(compFactory);
}

but there's a problem with the ApplicationRef#bootstrap since it doesn't take an injector or providers anymore. You're better off doing this outside of NgModule and create an NgModule for each app. Or use Di in ngDoBootstrap to grab your service that way it's created during bootstrap rather than later moduleRef.injector.get(MyService); // this makes sure that the service is created during bootstrap rather than later when something else injects it

like image 85
gdi2290 Avatar answered Nov 15 '22 07:11

gdi2290