3

I'm trying to have my custom component tags in a string array and bind them by ngfor to the innerhtml property after sanitizing them by calling bypassSecurityTrustHtml... unfortunately the output is always empty, but there is also no sanitize error...

What am i doing wrong?

// adminpanel.component.ts
@Component({
    selector: 'admin-panel',
    templateUrl: './adminpanel.component.html'
})
export class AdminPanelComponent {

    static GetRoutes(): Route[] {
        return [
            { path: '', redirectTo: 'news', pathMatch: 'full' },

        // 0
            { path: 'news', component: AdminNewsViewComponent },
        // 1
            { path: 'users', component: AdminUsersViewComponent },
        // 2
            { path: 'roles', component: AdminRolesViewComponent },
        // 3
            {
                path: 'culturesettings',
                redirectTo: 'culturesettings/wordvariables'
            },
            {
                path: 'culturesettings', 
                component: AdminCultureSettingsViewComponent,
                pathMatch: 'prefix',
                children: AdminCultureSettingsViewComponent.GetRoutes()
            },
        // 4
            {
                path: 'account',
                component: AdminAccountViewComponent
            }
        ]
    }

    panels: AdminPanel[] = [];

    routedTabs: RoutedTabs

    constructor(private authService: AuthService, private routerService: RouterService, private sanitizer: DomSanitizer) {
        this.routedTabs = new RoutedTabs("admin/panel", 2, authService, routerService);

        var routes = AdminPanelComponent.GetRoutes().filter(x => x.component != undefined);
        var comps = [
            '<admin-news-view></admin-news-view>',
            '<admin-users-view></admin-users-view>',
            '<admin-roles-view></admin-roles-view>',
            '<admin-culture-settings-view></admin-culture-settings-view>',
            '<admin-account-view></admin-account-view>'
        ];
        for (var i = 0; i < comps.length; i++) this.panels.push(new AdminPanel(i, routes[i], this.sanitizer.bypassSecurityTrustHtml(comps[i])  , this.sanitizer));

    }

    ngOnInit() {

        this.routedTabs.MakeTabs(AdminPanelComponent.GetRoutes());
        this.routedTabs.Subscribe();
        this.routedTabs.Emit();
    }
    ngOnDestroy() {
        this.routedTabs.Unsubscribe()
    }
}
class AdminPanel {
    index: number;
    route: Route;
    innerHtml: any = '';
    constructor(index: number, route: Route, innerHtml: any, private sanitizer: DomSanitizer) {
        this.index = index;
        this.route = route;
        this.innerHtml = innerHtml;

    }
}

And in my adminpanel.component.html:

<mat-tab-group (selectedTabChange)="routedTabs.onTabChange($event)" [(selectedIndex)]="routedTabs.selectedTab">
    <mat-tab *ngFor="let panel of panels" label="{{ routedTabs.tabs[panel.index].label }}">
        <div [innerHTML]="panel.innerHtml">

        </div>
    </mat-tab>
</mat-tab-group>
Fy Z1K
  • 618
  • 1
  • 7
  • 21
  • Angular sanitizes all content by default - https://angular.io/guide/security#angulars-cross-site-scripting-security-model - why are you doing it yourself? – Wand Maker Jul 12 '18 at 18:44
  • @WandMaker because if i try to put out the string directly then the console warns me that some content has been sanitized and the '' for example will be treaten as a threat eventhough it's my own component that i want to display as a variable that i can use in a ngfor loop – Fy Z1K Jul 12 '18 at 20:20
  • I am not sure if I understood your question correctly? Do you want to render your own components by just putting the component tag as string into the innerhtml property? – SirDieter Jul 13 '18 at 05:52
  • @SirDieter Yes that's exactly what i want, how can i achieve this? – Fy Z1K Jul 13 '18 at 13:44
  • Just using the tag as a string won't work, as angular doesn't just create component instances if an element with matching selector pops up in the dom. – SirDieter Jul 13 '18 at 13:57
  • @SirDieter I see. I stumbled across the dynamic component loader https://angular.io/guide/dynamic-component-loader and i think that's what i'm narrowly after. But i was just stunned that there is not an easier solution to this. – Fy Z1K Jul 13 '18 at 14:05
  • You can take a look a look at my answer, there I present another option, maybe a bit simpler – SirDieter Jul 13 '18 at 14:13
  • Thank you for your answer. What i did first corresponds to your second answer. I prepared the template first where i've put every component tag in my template. the problem to this is if i add some new views then i have to add at least three lines of markup in every template for every mat-tab entry for every component that i write. You see where this is going... So i guess the right way to do so is to take the ComponentFactoryResolver as explained in your answer or at the angular Docs regarding the Dynamic Component Loader. – Fy Z1K Jul 13 '18 at 14:46

2 Answers2

2

From what I gather, this has not really been resolved in a satisfying or clean manner. I've been in the same boat and didn't find a good solution to loading components in dynamic strings either - so I've written my own with ngx-dynamic-hooks!

Some key points you might be interested in:

  • Finds all component selectors inside a string and autormatically loads the corresponding components in their place
  • Can even load components by other text pattern than their selectors, if that is what you need
  • Inputs and outputs can be set just like in a normal template and are automatically parsed from strings into actual variables for you
  • Components can be nested without restrictions and will appear in each others "ng-content"-slots as expected
  • You can pass live data from the parent component into the dynamically loaded components (and even use it to bind inputs/outputs in the content string)
  • You have meticulous control over which components are allowed to load on an outlet-to-outlet-basis and even which inputs/outputs you can give them
  • You can optionally configure components to lazy-load only when they are needed
  • The library uses Angular's built-in DOMSanitizer to be safe to use even with potentially unsafe input

The components are created with native Angular methods and behave just like any other component in your app. I hope this helps all who enounter the same problem.

See it in action in this Stackblitz.

Mvin
  • 385
  • 4
  • 12
  • Upvoted for a satisfying matter, this would have served me very well 2 years ago.. Unforutunately this issue has already been solved using the Component Factory Resolver from the previous answer... But you are right this Answer is written in a satisfying AND clean manner! Hope it serves people in the future looking for the same answer as I did. – Fy Z1K Aug 26 '20 at 10:43
  • 1
    Thanks! Yeah, I figured that the issue is probably long resolved, but it hopefully might safe others some effort. – Mvin Aug 26 '20 at 13:38
1

Just using the tag as a string won't work, as angular doesn't just create component instances if an element with matching selector pops up in the dom.

You either

  1. use the ComponentFactoryResolver
  2. just have a type property on panel so you use ngSwitch based on type and render the corresponding in an ngSwitchCase. In that case you would have the tags in your template though
SirDieter
  • 309
  • 2
  • 9