2

I am learning Angular, I have a CMS with API that returns contents of pages. pages can have short codes for forms, I have updated the API to replace short with a component selector

Sample page contents would look like

<div>bla bla bla </div>
<app-form [id]="1"></app-form>

In angular I have created FormComponent to load form accordingly, but when I get page contents with above mentioned selector I get the error

'app-form' is not a known element:.....

I did some research and found that I need some dynamic component loader but could not found any working examples as per my scenario, can any one help on how I can fix this issue

1 Answer 1

1

Indeed you would have to create those components dynamically. See this plunkr for an example code to do that: https://plnkr.co/edit/kkM1aR4yPcIqeBhamoDW?p=info

Although you need a ViewContainer for Angular to know where to insert that dynamic component. Which would not work because you can't bind to innerHTML and then change the code of the innerHTML manually. I am not sure but I think that would mess with angulars change detection.

I had to do this a few months ago and came up with a solution. I want to mention at this point that I am not sure if there is a better solution to this problem by now. Anyway, what I did is not to create dynamic components but rather create some custom rendering with *ngIfs. Let me explain: Your content contains tags. You decide how those tags look like. In my case I had a tag that the user can insert wherever he wants: [galerie="key_of_gallery"]. So the content could look like

some normal text
 <h2>Oh what a nice heading</h2>
 [galerie="summer_gallery"]
 text again

Now how can I render this? I would have to get something like

 <div>
    some normal text
    <h2>Oh what a nice heading</h2>
 </div>
 <galerie [key]="summer_gallery"></galerie>
 <div>
     text again
 </div>

So I created a custom component which creates this:

import { Component, Input } from '@angular/core';

@Component({
    selector: 'ffam-render-key-value',
    template: `<div *ngFor="let contentToRender of contentArray">
                    <div *ngIf="contentToRender.type==='normal'" [innerHTML]="contentToRender.value">
                    </div>
                    <galerie *ngIf="contentToRender.type==='gallery'" [key]="contentToRender.key"></galerie>
                </div>`
})
export class NoeRenderKeyValueComponent{
    @Input('contentArray') contentArray: any[] = [];
}

All this component needs is an array of tags which will be rendered with an *ngFor. Depending on the type of the tag either normal HTML or a component is created.

This component can be inserted like

    <ffam-render-key-value [contentArray]="keyValues['_text'].arrayWithCustomTags">
    </ffam-render-key-value>

To get this array of tags I have created a service with a function:

public getArrayWithCustomTags(keyValue): any[] {
        let arrayWithCustomTags: any[] = [];
        //check if custom Tag exists in the innerHTML
        if (keyValue.value.indexOf('[galerie=') !== -1) {
            //replace double quotes
            keyValue.value = keyValue.value.replace(/&quot;/g, '"');
            //it exists, get array of all tags
            //regex that matches all [galerie="SOME KEY"] or [someAttribute="some text here"] -> You have to change this regex to fit all your tags
            let pattern = /(?:(\[galerie=\"[^\"]+\"\]))+/;
            //split with regexp to get array
            let arrayOfContents: string[] = keyValue.value.split(new RegExp(pattern, 'gi'));
            for (let i = 0; i < arrayOfContents.length; i++) {
                if (typeof arrayOfContents[i] === "undefined") {
                    arrayOfContents.splice(i, 1);
                    i--;
                }
                else {
                    let customTagToBeInserted: any = {};
                    if (arrayOfContents[i].indexOf('[galerie=') !== -1) {
                        //custom tag gallery
                        customTagToBeInserted.type = "gallery";
                        //get key only
                        customTagToBeInserted.key = arrayOfContents[i].replace("[galerie=\"", "").replace("\"]", "");
                    }
                    //else if some other attribute or even create a switch () {}
                    else {
                        //insert the normalHtml sanitized
                        customTagToBeInserted.type = "normal";
                        customTagToBeInserted.value = this.sanitizer.bypassSecurityTrustHtml(arrayOfContents[i]);
                    }
                    arrayWithCustomTags.push(customTagToBeInserted);
                }
            }
        }
        else {
            arrayWithCustomTags.push({ type: "normal", value: this.sanitizer.bypassSecurityTrustHtml(keyValue.value)});
        }
        return arrayWithCustomTags;
    }

This will create an array like:

[0]: {type: "normal", value:"SecureHTML"},
[1]: {type: "gallery", key:"summer_gallery"},
[2]: {type: "normal", value:"SecureHTML"},

Well I think you get the idea. If you create a whole CMS with more tags I would recommend creating a function that easily creates this whole process (regex etc.) for a tag. This example code is just meant for one tag.

The result is that the components are rendered right where the user places them. I hope this helps you.

Btw, if you have editable key value pairs for the user you might find this helpful: https://github.com/bergben/ng2-ck-editable. It's a little module I built to make any div editable using a ck-editor.

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.