1

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:

  1. Moving to AOT rather than JIT. Proving to be problematic due to an internal library, but we're working through it.
  2. 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?

3
  • hey, did my answer help? Commented Jul 13, 2017 at 5:54
  • Hi @Maximus , it looks good, however I haven't had a chance to implement it yet, other pressing project issues :/ I'll try to get to it today or over the weekend, thanks for the help. Commented Jul 14, 2017 at 2:25
  • so how did it go? Commented Jul 17, 2017 at 18:00

1 Answer 1

1

You have a few options

1) Add lazy loaded components to their own modules and load and compiled those on demand as described in Here is what you need to know about dynamic components in Angular. You will need to use compileModuleAndAllComponentsAsync method. Here is the example:

ngAfterViewInit() {
  System.import('app/t.module').then((module) => {
      _compiler.compileModuleAndAllComponentsAsync(module.TModule)
        .then((compiled) => {
          const m = compiled.ngModuleFactory.create(this._injector);
          const factory = compiled.componentFactories[0];
          const cmp = factory.create(this._injector, [], null, m);
        })
    })
}

2) Another approach would be to AOT compiled components and load their factories on demand. Angular AOT compiler generates AOT output as valid ES6/TS modules, so you can import the components individually and create their instances right from the factories:

  System.import('components/ex.component.ngfactory').then((factory) => {
       const cmp = factory.create(this._injector, [], null, m);
  })

Or you can get the module factory with all the components and create in instance of it.

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.