Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically displaying elements containing HTML with (click) functions Angular 2

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.

like image 264
alsco77 Avatar asked Oct 25 '16 14:10

alsco77


2 Answers

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);

    }
  }
like image 142
Nikhil Shah Avatar answered Nov 17 '22 17:11

Nikhil Shah


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.

like image 3
Siri0S Avatar answered Nov 17 '22 18:11

Siri0S