We're hosting an angular2 app within a cms (Sitecore), we have a requirement that the content editors can add, remove and reorder components of our app within a page, they can also add other components as desired.
We do this by having the cms generate script tags to load our components. An example of generated html is:
<body>
<sales-root></sales-root>
<script type="text/html" id="generated-header">
<sales-welcome></sales-welcome>
</script>
<script type="text/html" id="generated-content">
<sales-personal-info></sales-personal-info>
<div><!-- regular html content --></div>
</script>
</body>
In the sales-root component we have
export class AppComponent extends Translation implements OnInit {
@ViewChild('header', { read: ViewContainerRef }) headerRef;
@ViewChild('content', { read: ViewContainerRef }) contentRef;
ngOnInit() {
this.loadSitecorePlaceholders(this.headerRef, 'generated-header');
this.loadSitecorePlaceholders(this.contentRef, 'generated-content');
// ...
}
private loadSitecorePlaceholders(containerRef: ViewContainerRef, placeholder: string) {
// get the generated components listed from the sitecore placeholder
const generatedContent = this.elementRef.nativeElement.parentNode.children[placeholder];
if (generatedContent === undefined) { return; }
if (containerRef === undefined) { return; }
this.createComponentService.createComponentsFromHtml(containerRef, generatedContent.innerText);
// we've finished creating all the components we need, remove the nodes created by sitecore.
this.elementRef.nativeElement.parentNode.removeChild(generatedContent);
}
}
Within CreateComponentService we have intialise an array of allowable component factories, and a generic htmlHost component factory:
this._selectableFactories = creatableComponents
.map((component: Type<any>) =>
this.componentFactoryResolver.resolveComponentFactory(component));
We parse the CMS generated script tag for selectors, and, if we find them we add the component, or we inject into the generic html component:
private createAngularComponent(
container: ViewContainerRef,
factory: ComponentFactory<Type<any>>,
element: HTMLElement,
) {
const selector = factory.selector.toLocaleLowerCase();
container.createComponent(factory);
// ...
}
private createHtmlComponent(container: ViewContainerRef, element: HTMLElement) {
const component = container.createComponent(this.htmlHostFactory).instance;
component.content = element.outerHTML;
}
All this is working quite well, except for performance. We have many components and loading and compiling them all takes a while (3 to 4 seconds on a fast machine). We're tackling this in 2 ways:
- Moving to AOT rather than JIT. Proving to be problematic due to an internal library, but we're working through it.
- Lazy loading the components as they are required. We only ever require a subset of components on any page, so this should give us some gains.
With #2 we need to have all the creatable components listed in entryComponents in the @NgModule, so they must all exist as it's generated... right? I'm also pre-loading the factories so I can find the selectors, but I could build that list in development.
Ideally I'd have a dictionary of selectors to lazy component factory factories, first call would download the component and load it then inject it, this would happen post ngInit and would also give the appearance of a faster load.
Is it possible to lazy load the components? So far all the examples I've seen are using the router and we aren't due to the dynamic composition. How?