2

I have a simple component:

@Component({
  selector: '[group_id]'
})
export class ItemComponent {}

I also have a component where I try to get all children ItemComponent that are passed to this component as a content.

@Component({
    selector    : 'selector',
    template    : '<ng-content></ng-content>',
})
export class SelectorComponent implements AfterContentInit {
    @ContentChildren(ItemComponent) items;

    ngAfterContentInit() {
        console.log (this.items);
    }
}

Now I use my SelectorComponent somewhere in the application:

<selector>
    <div [innerHTML]="'<p group_id>A</p>' | safeHtml"></div>

    <p group_id>B</p>
    <p group_id>C</p>
    <p group_id>D</p>
</selector>

In ngAfterContentInit hook of SelectorComponent get only 3 results (B, C, D elements). How do I access element A if it was a SafeValue?

Any way to skip requirement SafeValue must use [property]=binding (XSS protection)? Is there other way to access this component (I'm not interested in accessing DOM element but to get the component reference).

Baumi
  • 1,745
  • 1
  • 17
  • 31

2 Answers2

1

Angular doesn't know about things added using [innerHTML] or other HTML added dynamically at runtime. Angular compiles templates when the application is built, before it is deployed, and ignores all HTML added at runtime.

You can either do it imperatively

constructor(private elRef:ElementRef) {}

ngAfterContentInit() {
  this.elRef.nativeElement.querySelector(...)
}

or load the dynamic platform to compile a component atr untime like explained in How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I will follow your second advise - compile a component at runtime :) But this might not work with AoT, as I would need to send compiler also with the app, am I right? – Baumi Dec 13 '17 at 09:33
  • 1
    I don't know the status of this. There were problems a while ago but also hacks to make it work (all discussed in GitHub issues). I couldn't find the one just know that I remember, but I found a newer one https://github.com/angular/angular/issues/20875 Don't know if this helps because I didn't have a closer look at Angular Elements myself yet. – Günter Zöchbauer Dec 13 '17 at 09:38
1

Thanks to @Günter Zöchbauer and link he has posted in one of the comments and the answer here I have found a solution:

I decided to use ngComponentOutlet that as docs says:

NgComponentOutlet provides a declarative approach for dynamic component creation

First I made a dynamic component:

import {Component, OnChanges, OnInit, Input, NgModule,NgModuleFactory,Compiler, SimpleChanges} from '@angular/core';
import { SharedModule } from './shared.module';

@Component({
    selector: 'dynamic',
    template: `<ng-container *ngComponentOutlet="dynamicComponent;ngModuleFactory: dynamicModule;"></ng-container>`
})
export class DynamicComponent {

    dynamicComponent: any;
    dynamicModule   : NgModuleFactory<any>;

    @Input('html') html: string;

    constructor(private compiler: Compiler) {}

    ngOnChanges(changes: SimpleChanges) {
        if (changes['html'] && !changes['html'].isFirstChange()) {
            this.dynamicComponent = this.createNewComponent(this.html);
            this.dynamicModule    = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
        }
    }

    ngOnInit() {
        this.dynamicComponent = this.createNewComponent(this.html);
        this.dynamicModule    = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
    }

    protected createComponentModule(componentType: any) {
        @NgModule({
            imports     : [SharedModule],
            declarations: [
                componentType
            ],
            entryComponents: [ componentType ]
        })
        class RuntimeComponentModule {
        }
        // a module for just this Type
        return RuntimeComponentModule;
    }

    protected createNewComponent(template: string) {

        @Component({
            selector: 'dynamic-component',
            template: template ? template: '<div></div>'
        })
        class MyDynamicComponent {}

        return MyDynamicComponent;
    }
}

Then I imported my dynamic component to my module:

import { DynamicComponent } from './dynamic-item/dynamic-item';

@NgModule({
    declarations: [
        ...
        DynamicComponent,
    ],
    exports: [
        ...
        DynamicComponent,
    ]
})
export class ComponentsModule { }

No i could use my dynamic component with any dynamically loaded html template inside:

<dynamic [html]="dynamicHtml"></dynamic>

where dynamicHtml can by any html containing any existing component / directive / etc.:

dynamicHtml: string = "<p group_id>A</p>";

What is worth to notice is that this approach requires JIT compiler so while developing everything works fine, but after compilation with AoT I get an error when running:

ERROR Error: Runtime compiler is not loaded

Under the original question in one of the comments @Günter Zöchbauer has posted a link to feature request tht can solve that problem. For now if you want to use this approach do not use AoT compilation.

Baumi
  • 1,745
  • 1
  • 17
  • 31