11

I have a recursive tree structure containing nodes which each have an 'htmlStringContent' property. When I display the tree using nested 'node' components and try to present the html content I use:

<div [innerHtml]="node.htmlStringContent"></div>

The HTML displays correctly but for the following elements:

<a (click)="function()">click me</a>

The (click) functions don't work. I know this has previously been posted but with the large amount of updates angular has brought out recently I cant find any solutions. This answer leads me to believe I should be using the ngComponentOutlet directive but I'm not sure how..

How can I get angular to bind this click functionality?

Edit: I have been told to use the ComponentFactoryResolver but can't see how I can use this to display the html correctly. Can anyone provide further help?

Edit2: I am parsing 'htmlStringContent' through a sanitizing pipe before displaying it on [innerHtml]

transform(v: string) : SafeHtml {
  return this._sanitizer.bypassSecurityTrustHtml(v); 
} 

Edit3: Basically this question is asking whether it is as all possible to display HTML from a property on an object in angular 2/ionic 2 while retaining the (click) functionality on it. I am also open to workaround answers.

7
  • This way click event will not work. You have a create component and then you will be able to generate click event as already shown in one of your links. Commented Oct 25, 2016 at 14:43
  • @micronyks but i will still have to use [innerHtml] to present the html content? Commented Oct 25, 2016 at 14:44
  • 1
    I understand that but like angular1, angular2 doesn't have $compile service so you can do it using componentFactoryResolver. Commented Oct 25, 2016 at 14:46
  • @micronyks Not entirely sure how to use that.. could you be more elaborate? Commented Oct 25, 2016 at 15:02
  • With tree structure I'm not sure but when template has angular context you should use CFR only. Commented Oct 25, 2016 at 15:22

2 Answers 2

4

CFR DEMO : https://plnkr.co/edit/jKEaDz1JVFoAw0YfOXEU?p=preview

@Component({
  selector: 'my-app',
  template: `

     <button (click)="addComponents()">Add HTML (dynamically using CRF)</button>

     <h1>Angular2 AppComponent</h1>
     <hr>

     <div>
     <h5>dynamic html goes here</h5>
      <div class="container">
        <template #subContainer1></template>
      </div>
     </div>


  `,

})
export class App {
    name:string;
    @ViewChild('subContainer1', {read: ViewContainerRef}) subContainer1: ViewContainerRef;

    constructor(
      private compFactoryResolver: ComponentFactoryResolver
      ) {
      this.name = 'Angular2'
    }

    addComponents() {

      let compFactory: ComponentFactory;

      compFactory = this.compFactoryResolver.resolveComponentFactory(Part1Component);
      this.subContainer1.createComponent(compFactory);

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

1 Comment

I see that this dynamically creates components.. but I have already a nested structure of components that display using recursive ngfor, how will this solution present the html string contained in the components properties and present it correctly, inclusive of the click functionality??
3

If I understand correctly, you need to use dynamic templates and compile them on runtime. If so, then you need to use the angular compiler:

@Component({
    selector: 'my-app',
    template: `
      <h1>Angular 2 Dynamic Component</h1>
      <template #container></template>
    `
})
export class AppComponent implements AfterContentInit, OnDestroy {
  private dynamicComponentRefs: ComponentRef[] = [];

  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  constructor(private resolver: ComponentFactoryResolver,
              private compiler: Compiler) {}

  ngAfterContentInit() {
    let html = `
      <div>Always Visible</div>
      <div [hidden]="clause1">Hidden because of clause1 = true</div>
      <div [hidden]="clause2">Visible because of clause2 = false</div>
      <button type="button" (click)="buttonClicked()">Click me!</button>
      <div *ngIf="clicked">You clicked the button!</div>
    `;

    this.compiler.compileModuleAndAllComponentsAsync(createDynamicComponent(html))
        .then((mwcf: ModuleWithComponentFactories) => {
          let factory: ComponentFactory = mwcf.componentFactories.find(cf => 
            cf.componentType.name === 'DynamicComponent');
            this.dynamicComponentRefs
              .push(this.container.createComponent(factory));
        });
  }

  ngOnDestroy() {
    /* Make sure you destroy all dynamically created components to avoid leaks */
    this.dynamicComponentRefs.forEach(dcr => {
      dcr.destroy();
    });
  }

}

export function createDynamicComponent(html: string): Type<NgModule> {
  @Component({
    template: html,
  })
  class DynamicComponent {
    private clause1: boolean = true;
    private clause2: boolean = false;
    private clicked = false;

    buttonClicked() {
      this.clicked = true;
    }
  }

  @NgModule({
    imports: [CommonModule],
    declarations: [DynamicComponent],
  })
  class DynamicComponentModule {}

  return DynamicComponentModule;
}

Basically you need to dynamically create a component and the module that declares it (e.g. through a function) and pass it the template as an argument. Then you can call compileModuleAndAllComponentsAsync() on the module and get the component factory you need. Then it's a matter of rendering it in the DOM through the ViewContainerRef.createComponent() method.

Here is working plunker: dynamic template component

Keep in mind however that - for the moment - this approach can only be used with JIT compilation. In AOT compilation the angular compiler is not available and trying to use it will throw an error.

Comments

Your Answer

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