最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular 2.1.0 create child component on the fly, dynamically - Stack Overflow

programmeradmin11浏览0评论

What i'm trying to do in angular 2.1.0 is creating child components on the fly which should be injected into parent component. For example parent component is lessonDetails which contains shared stuff for all lessons such as buttons like Go to previous lesson, Go to next lesson and other stuff. Based on route params, lesson content which should be child component needs to be injected dynamically into parent component. HTML for child components (lesson content) is defined as plain string somewhere outside, it can be object like:

export const LESSONS = {
  "lesson-1": `<p> lesson 1 </p>`,
  "lesson-2": `<p> lesson 2 </p>`
}

Problem can be easily solved through innerHtml having something like following in parent component template.

<div [innerHTML]="lessonContent"></div>

Where on each change of route params, property lessonContent of parent component would change(content(new template) would be taken from LESSON object) causing parent component template to be updated. This works but angular will not process content injected through innerHtml so it is impossible to use routerLink and other stuff.

Before new angular release i solved this problem using solution from /, where i have been using ComponentMetadata together with ComponentResolver to create child components on the fly, like:

const metadata = new ComponentMetadata({
  template: this.templateString,
});

Where templateString was passed to child component as Input property to child component. Both MetaData and ComponentResolver are deprecated/removed in angular 2.1.0.

So problem is not just about dynamic component creation, like described in few related SO questions, problem would be easier to solve if i would have defined component for each lesson-content. This would mean that i need to predeclare 100 different components for 100 different lessons. Deprecated Metadata was providing behaviour that was like updating template at runtime of single component(creating and destroying single component on route params change).

Update 1: As it seems in recent angular release, all components that needs to be created/injected dynamically needs to be predefined in entryComponents within @NgModule. So as it seems to me, related to question above, if i need to have 100 lessons(components that needs to be created dynamically on the fly) that means i need to predefine 100 components

Update 2: Based on Update 1, it can be done through ViewContainerRef.createComponent() in following way:

// lessons.ts
@Component({ template: html string loaded from somewhere })
class LESSON_1 {}

@Component({ template: html string loaded from somewhere })
class LESSON_2 {}

// exported value to be used in entryComponents in @NgModule
export const LESSON_CONTENT_COMPONENTS = [ LESSON_1, LESSON_2 ]

Now in parent component on route params change

const key = // determine lesson name from route params

/**
 * class is just buzzword for function
 * find Component by name (LESSON_1 for example)
 * here name is property of function (class)
 */

const dynamicComponent = _.find(LESSON_CONTENT_COMPONENTS, { name: key });
const lessonContentFactory = this.resolver.resolveComponentFactory(dynamicComponent);
thisponentRef = this.lessonContent.createComponent(lessonContentFactory);

Parent template looks like:

<div *ngIf="something" #lessonContentContainer></div>

Where lessonContentContainer is decorated @ViewChildren property and lessonContent is decorated as @ViewChild and it is initialized in ngAfterViewInit () as:

ngAfterViewInit () {
  this.lessonContentContainer.changes.subscribe((items) => {
    this.lessonContent = items.first;
    this.subscription = this.activatedRoute.params.subscribe((params) => {
      // logic that needs to show lessons
    })
  })
}

Solution has one drawback and that is, all components(LESSON_CONTENT_COMPONENTS) needs to be predefined.
Is there a way to use one single component and to change template of that component at runtime (on route params change)?

What i'm trying to do in angular 2.1.0 is creating child components on the fly which should be injected into parent component. For example parent component is lessonDetails which contains shared stuff for all lessons such as buttons like Go to previous lesson, Go to next lesson and other stuff. Based on route params, lesson content which should be child component needs to be injected dynamically into parent component. HTML for child components (lesson content) is defined as plain string somewhere outside, it can be object like:

export const LESSONS = {
  "lesson-1": `<p> lesson 1 </p>`,
  "lesson-2": `<p> lesson 2 </p>`
}

Problem can be easily solved through innerHtml having something like following in parent component template.

<div [innerHTML]="lessonContent"></div>

Where on each change of route params, property lessonContent of parent component would change(content(new template) would be taken from LESSON object) causing parent component template to be updated. This works but angular will not process content injected through innerHtml so it is impossible to use routerLink and other stuff.

Before new angular release i solved this problem using solution from http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2/, where i have been using ComponentMetadata together with ComponentResolver to create child components on the fly, like:

const metadata = new ComponentMetadata({
  template: this.templateString,
});

Where templateString was passed to child component as Input property to child component. Both MetaData and ComponentResolver are deprecated/removed in angular 2.1.0.

So problem is not just about dynamic component creation, like described in few related SO questions, problem would be easier to solve if i would have defined component for each lesson-content. This would mean that i need to predeclare 100 different components for 100 different lessons. Deprecated Metadata was providing behaviour that was like updating template at runtime of single component(creating and destroying single component on route params change).

Update 1: As it seems in recent angular release, all components that needs to be created/injected dynamically needs to be predefined in entryComponents within @NgModule. So as it seems to me, related to question above, if i need to have 100 lessons(components that needs to be created dynamically on the fly) that means i need to predefine 100 components

Update 2: Based on Update 1, it can be done through ViewContainerRef.createComponent() in following way:

// lessons.ts
@Component({ template: html string loaded from somewhere })
class LESSON_1 {}

@Component({ template: html string loaded from somewhere })
class LESSON_2 {}

// exported value to be used in entryComponents in @NgModule
export const LESSON_CONTENT_COMPONENTS = [ LESSON_1, LESSON_2 ]

Now in parent component on route params change

const key = // determine lesson name from route params

/**
 * class is just buzzword for function
 * find Component by name (LESSON_1 for example)
 * here name is property of function (class)
 */

const dynamicComponent = _.find(LESSON_CONTENT_COMPONENTS, { name: key });
const lessonContentFactory = this.resolver.resolveComponentFactory(dynamicComponent);
this.componentRef = this.lessonContent.createComponent(lessonContentFactory);

Parent template looks like:

<div *ngIf="something" #lessonContentContainer></div>

Where lessonContentContainer is decorated @ViewChildren property and lessonContent is decorated as @ViewChild and it is initialized in ngAfterViewInit () as:

ngAfterViewInit () {
  this.lessonContentContainer.changes.subscribe((items) => {
    this.lessonContent = items.first;
    this.subscription = this.activatedRoute.params.subscribe((params) => {
      // logic that needs to show lessons
    })
  })
}

Solution has one drawback and that is, all components(LESSON_CONTENT_COMPONENTS) needs to be predefined.
Is there a way to use one single component and to change template of that component at runtime (on route params change)?

Share Improve this question edited Oct 17, 2016 at 12:42 Mistalis 18.3k13 gold badges77 silver badges97 bronze badges asked Oct 15, 2016 at 14:53 SrleSrle 10.5k9 gold badges35 silver badges67 bronze badges 4
  • See hl7.org/fhir/StructureDefinition/…. Adding HTML just adds HTML, if you want components dynamically you can use ViewContainerRef.createComponent(). Otherwise components and directives are only created for selectors that are added statically to the template of a component. – Günter Zöchbauer Commented Oct 16, 2016 at 19:35
  • @GünterZöchbauer thank you for reply, actually i'm using ViewContainerRef.createComponent() please check Update 2 part in question – Srle Commented Oct 16, 2016 at 20:13
  • You can't modify the template of a component at runtime. There are ways to create new components at runtime. I don't know details about this but there are answers to similar questions on SO. – Günter Zöchbauer Commented Oct 17, 2016 at 5:35
  • Similar issue is covered here How can I use/create dynamic template to compile dynamic Component with Angular 2.0? – Radim Köhler Commented Oct 17, 2016 at 13:03
Add a comment  | 

1 Answer 1

Reset to default 16

You can use the following HtmlOutlet directive:

import {
  Component,
  Directive,
  NgModule,
  Input,
  ViewContainerRef,
  Compiler,
  ComponentFactory,
  ModuleWithComponentFactories,
  ComponentRef,
  ReflectiveInjector
} from '@angular/core';

import { RouterModule }  from '@angular/router';
import { CommonModule } from '@angular/common';

export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
    const cmpClass = class DynamicComponent {};
    const decoratedCmp = Component(metadata)(cmpClass);

    @NgModule({ imports: [CommonModule, RouterModule], declarations: [decoratedCmp] })
    class DynamicHtmlModule { }

    return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
       .then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
        return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
      });
}

@Directive({ selector: 'html-outlet' })
export class HtmlOutlet {
  @Input() html: string;
  cmpRef: ComponentRef<any>;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }

  ngOnChanges() {
    const html = this.html;
    if (!html) return;

    if(this.cmpRef) {
      this.cmpRef.destroy();
    }

    const compMetadata = new Component({
        selector: 'dynamic-html',
        template: this.html,
    });

    createComponentFactory(this.compiler, compMetadata)
      .then(factory => {
        const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);   
        this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
      });
  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }    
  }
}

See also Plunker Example

Example with custom component

For AOT compilation see these threads

  • https://github.com/angular/angular/issues/15510
  • http://blog.assaf.co/angular-2-harmony-aot-compilation-with-lazy-jit-2/

See also github Webpack AOT example https://github.com/alexzuza/angular2-build-examples/tree/master/ngc-webpack

发布评论

评论列表(0)

  1. 暂无评论