Let's say I have a module with components and services which should only be loaded if a specific feature flag is true. The app.component checks for the feature flag and in its template has a component to render dynamically injected components.
I can load the module conditionally:
private async checkOnboardingStatus() {
if ( true ) {
const { OnboardingModule } = await import('./onboarding/onboarding.module');
/**
* magic happens here
* and we get an instance of OnboardingComponent
**/
/**
* DRC this is DynamicRendererComponent which expects this structure
*/
this.drc.data = {comp: OnboardingComponent, data: {}};
}
}
The whole point of this lazy loading is that the module will not be needed later in the lifecycle of the app and is only needed in the very beginning, maybe one more time with the next load. I just don't want to add it to 'shared' module and needlessly drag it around.
This was harder to find than I expected. There are a lot of answers around about dynamically rendering a component which is already loaded. There are enough docs to understand lazy loading routed modules. However lazy loading a module without a route and get a component instance, which you can later render dynamically is somewhat tricky but it can be done.
The trick is to provide a resolver service within the module and use ViewContainerRef from the 'parent' component to create the component you need.
Here is the module definition which exposes the resolver service:
@NgModule({
declarations: [
OnboardingGuideComponent,
...
],
imports: [
CommonModule,
...
],
providers: [OnboardingResolverService],
})
export class OnboardingModule {
public static getProviders(): Array<any> {
return [OnboardingResolverService];
}
}
Here is the OnboardingResolverService:
@Injectable({
providedIn: 'root'
})
export class OnboardingResolverService {
public resolveComponent(vcr: ViewContainerRef): ComponentRef<OnboardingGuideComponent> {
return vcr.createComponent(OnboardingGuideComponent);
}
}
And here is the method in app.component which actually loads the module and get the component instance which can be later rendered dynamically:
@ViewChild('dynamicComponent', { read: ViewContainerRef, static: true }) vcr!: ViewContainerRef;
/** ........... */
private async loadOnboarding(){
const { OnboardingModule } = await import('./onboarding/onboarding.module');
const moduleRef = Injector.create({providers: OnboardingModule.getProviders()});
const onboardingResolver = moduleRef.get(OnboardingResolverService);
const componentRef = onboardingResolver.resolveComponent(this.vcr);
this.dcr = {
comp: componentRef.componentType, /** my implementation of dynamic renderer expects a Type, yours can expect a component instance */
data: {}
};
}
Anyways, this seems to be working for now. Credit for the idea goes to https://medium.com/@ckyidr9/lazy-load-feature-modules-without-routing-in-angular-9-ivy-220851cc7751