1

I'm trying to make Angular 2 dynamically compile a fragment of HTML using RuntimeCompiler. I have found the solution in this post (see Angular 2.0 RC 5 related answer) and it works until I use templateUrl instead of in-line template in the decorator of component.

@Component({
  selector: 'dynamic-detail',
  providers: [DynamicTypeBuilder, DynamicTemplateBuilder],
  templateUrl: 'app/dynamic/detail.view.html',
})

When I use templateUrl, I get the following error:

Unhandled Promise rejection: EXCEPTION: Error in ./AppComponent class AppComponent - inline template:4:3
ORIGINAL EXCEPTION: Error: No XHR implementation has been provided. Can't read the url "app/dynamic/detail.view.html"
ORIGINAL STACKTRACE:
Error: No XHR implementation has been provided. Can't read the url "app/dynamic/detail.view.html"

It looks like during dynamic compilation Angular loses it's Xhr implementation so it can't load html-template through http. Can anybody help with it?

You can find my example on plunker. version 3 - example with templateUrl(content isn't loaded dynamically) version 2 - example with in-line template(everythink works fine).

Community
  • 1
  • 1

2 Answers2

0

Add moduleId: module.id in the @Component decorator.

Check this article for details.

sbeci
  • 31
  • 2
  • Thank you, @sbeci. Unfortunately your solution doesn't work - if it was the problem with module.id I would just get 404 error - but my error is more tricky – Vavilov Vitaliy Aug 25 '16 at 13:22
0

a similar "bug" was raised in the issue of Angular : https://github.com/angular/angular/issues/11015

If you follow my snippet, you can use templateUrl but only for "container", not for the "widget" :

import {
  Component,
  ComponentFactoryResolver,
  ViewContainerRef,
  ViewChild,
  OnInit,
  Compiler
} from '@angular/core';
import {RuntimeCompiler} from "@angular/compiler";
import {EmptyModule} from "./empty.module";

export class MyComponentLoader {
  loadComponentConfig(url) {
    return fetch(url)
      .then(res => res.json())
      .then(componentList => Promise.all(
        componentList.map(config => this.loadComponent(config))
        )
      );
  }

  loadComponent(configObject) {
    return System.import(configObject.path)
                 .then(componentModule =>
                   componentModule[configObject.component]
                 )
  }
}

@Component({
  moduleId: module.id,
  selector: 'ng-smartdesk',
  templateUrl: './smartdesk.component.html',
  providers: [MyComponentLoader],
})
export class SmartdeskComponent implements OnInit {
  @ViewChild('widgets', {read: ViewContainerRef})
  container: ViewContainerRef;

  constructor(private _loader: MyComponentLoader,
              protected _compiler: RuntimeCompiler) {
  }

  ngOnInit() {
    this._loader.loadComponentConfig('/app/config.json')
        .then(components => {
          if (components) {
            Promise.all(components.map(test => this.loadComp(test, null)));
          }
        }, error => console.log(error));
  }

  private loadComp(comp: Component, index: number) {
    this._compiler
        .compileComponentAsync(comp, EmptyModule)
        .then(factory => {
          this.container.createComponent(factory, index, this.container.injector)
        });
  }
}

Edit : SOLVED ! with the help of Tobias Bosch, the solution is to create a new instance of a compiler. This is my final code :

import {
  Component,
  ComponentFactoryResolver,
  ViewContainerRef,
  ViewChild,
  OnInit,
  Compiler, CompilerFactory
} from '@angular/core';
import {RuntimeCompiler} from "@angular/compiler";
import {EmptyModule} from "./empty.module";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";

export class MyComponentLoader {
  loadComponentConfig(url) {
    return fetch(url)
      .then(res => res.json())
      .then(componentList => Promise.all(
        componentList.map(config => this.loadComponent(config))
        )
      );
  }

  loadComponent(configObject) {
    return System.import(configObject.path)
                 .then(componentModule =>
                   componentModule[configObject.component]
                 )
  }
}

@Component({
  moduleId: module.id,
  selector: 'ng-smartdesk',
  templateUrl: './smartdesk.component.html',
  providers: [MyComponentLoader],
})
export class SmartdeskComponent implements OnInit {
  @ViewChild('widgets', {read: ViewContainerRef})
  container: ViewContainerRef;
  private _compiler;

  constructor(private _loader: MyComponentLoader) {
    const compilerFactory : CompilerFactory = platformBrowserDynamic().injector.get(CompilerFactory);
    this._compiler = compilerFactory.createCompiler([]);
  }

  ngOnInit() {
    this._loader.loadComponentConfig('/app/config.json')
        .then(components => {
          if (components) {
            Promise.all(components.map(test => this.loadComp(test, null)));
          }
        }, error => console.log(error));
  }

  private loadComp(comp: Component, index: number) {
    this._compiler
        .compileComponentAsync(comp, EmptyModule)
        .then(factory => {
          this.container.createComponent(factory, index, this.container.injector)
        });
  }
}
cyberbobjr
  • 239
  • 2
  • 6