0

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.

1
  • A standalone component would probably not work as the module is supposed to hold a couple of more dedicated components. So all of it is needed. Commented Mar 5 at 20:19

2 Answers 2

0

I also tried falling back to a routed module for a secondary router outlet, but then it gets complicated, as the URL gets updated with some (secondary: onboarding) string and - if one uses skipLocationChange - needs to be constantly monitored not to unload the dynamically loaded module.

So I am still looking for a solution to display a component from a dynamically loaded module (with further components, interfaces and services) which doesn't affect routing and stays persistent across the app. Any ideas are greatly appreciated.

Sign up to request clarification or add additional context in comments.

Comments

0

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.