Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular lazy loading dynamic component

Using Angular 4.1.3, I'm working on a mapping application which lazy-loads various tool modules. Currently, there is a router-outlet, which places the tool within the map. However, I need to support placing the tool in a new tab, next to the map. The tab is dynamically created in response to loading the tool module, and there can be multiple tabs open simultaneously.

Creating a tab component is simple enough, and it seems like putting a dynamic component loader in the new tab (https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html), is a reasonable way to place the tool in the tab. However, I have no idea how to actually get a reference to the tool's component for passing it to the loader, since it's part of a lazy-loaded module. Is there a way to have the router pass this to my app.component instead of outputting it via a router-outlet? I've thought about putting the router-outlet in a hidden div and then binding to it in the controller, but that seems hacky, and I'm not sure it would work.

Some similar questions/answers I've seen seemed to depend on SystemJS, but I'm using Webpack.

EDIT: I got a basic plunker running here: http://plnkr.co/edit/RPbyQZ4LyHN9o9ey2MJT?p=preview

The code there has a very basic component dynamically added to a tab

  export class ContentPaneComponent implements OnInit {
  @ViewChild('contentpane') el: ElementRef;

  @Output() newContent: EventEmitter<any> = new EventEmitter();

  private content: string;
  private contentSubscription: Subscription;

  private tab1 = new CompItem(htmlPaneComponent, {});
  // private tab2 = new CompItem(LayerManagerComponent, {});

  ngOnInit(): void {
  }

}

the component which is specified in tab1 has to be included in the entrycomponents of content-pane.module entryComponents: [htmlPaneComponent]

Ultimately, I want to be creating new CompItem()s when modules are lazy-loaded. When I activate a route such as:

{
    path: 'search', loadChildren:
    'components/conference-room-search/conference-room-search.module#ConferenceRoomSearchModule'
        }

I think what I need is for the router to give me a reference to the module or its components.

like image 551
Charlie Elverson Avatar asked Jun 01 '17 16:06

Charlie Elverson


1 Answers

Here's a strategy for dynamically and lazily loading a component. But please beware, I haven't tried it yet!

Get an NgModuleFactoryLoader instance via dependency injection and asynchronously obtain the module containing the component you wish to lazily load. From that, obtain an NgModuleFactory and then obtain an NgModuleRef.

The NgModuleRef will give you:

  • a ComponentFactoryResolver which accepts a Type<T> (from @angular/core)
  • instance() of the module class.

So you can't make a Type<T> from your code to pass to the ComponentFactoryResolver since your code can't depend on T, but the module has access to the T and can provide a Type<T>.

Make the module implement an interface that can return a Type<any> (the component class) and cast NgModuleRef.instance() to that interface. Then use the ComponentFactoryResolver. If that doesn't work for some reason, make the module call ComponentFactoryResolver and return the component directly rather than a Type. Once you have a ComponentFactory you can do dynamic component loading like normal.

like image 154
Alexander Taylor Avatar answered Sep 21 '22 01:09

Alexander Taylor