Closed ChazUK closed 2 years ago
Hello, @ChazUK
If you want to have using ng-content with dynamic components, you have to create projectableNodes
by self. I'll explain to you what do you need to do.
You have to create an <ng-template>
and put an <ng-content>
in it.
@Component({
selector: 'app-x-column',
template: `
<!-- 👇 place TemplateRef with NgContent here -->
<ng-template #templateRef>
<ng-content></ng-content>
</ng-template>
`
})
class XColumnComponent {}
Then you have to get the TemplateRef
using by @ViewChild()
decorator.
@Component({
selector: 'app-x-column',
template: `
<ng-template #templateRef>
<ng-content></ng-content>
</ng-template>
`
})
class XColumnComponent {
// 👇 getting access to the TemplateRef
@ViewChild('templateRef', {
static: true,
read: TemplateRef
})
templateRef: TemplateRef<{}>;
}
After that you need to create a projectableNodes
. In order to do this you have to create a ViewRef
of the TemplateRef
and get rootNodes
. Don't forget to place the projectableNodes
in *ngxComponentOutlet="content: projectableNodes"
.
@Component({
selector: 'app-x-column',
template: `
<ng-template #templateRef>
<ng-content></ng-content>
</ng-template>
<ng-container *ngxComponentOutlet="
<!-- putting 👇 projectableNodes into the content of NgxComponentOutlet -->
component; content: projectableNodes"></ng-container>
`
})
class XColumnComponent {
projectableNodes: any[][];
@ViewChild('templateRef', {
static: true,
read: TemplateRef
})
set templateRef(t: TemplateRef<{}>) {
// 👇 creating projectableNodes
this.projectableNodes = [
t.createEmbeddedView({}).rootNodes
];
};
}
this.viewRef = t.createEmbeddedView({});
// some later
this.viewRef.destroy();
Hey @ChazUK, did this solve your problem?
Hey @thekiba,
that looks very interesting. Would it also be possible to combine it with https://stackblitz.com/edit/angular-simple-dynamic?file=src%2Fapp%2Fapp.module.ts ?
Instead of [{component: InfoCardComponent, title: 'Info Card 1', content: <p>Complex Content</p>
}, {component: InfoCardComponent, title: 'Another Info Card', content: <p>Complex <a href="#">Content</a></p>
}]
I would like to use [{component: 'info-card', title: 'Info Card 1', content: <p>Complex Content</p>
}, {component: 'info-card', title: 'Another Info Card', content: <p>Complex <a href="#">Content</a></p>
}].
and then lazy load the InfoCardComponent, for example using the resolve method.
something like:
const type: Type<any> = await import('../components/info-card/info-card.component').then(module => module.InfoCardComponent'); return this.componentFactoryResolver.resolveComponentFactory(type);
Regards, Alex
Hello, @xeladotbe.
Could you please confirm that I understand you right: you want to use html string with dynamic components?
Hey @thekiba ,
sorry, I was a bit hasty with the copying. Of course I don't want to insert pre-generated markup. I've a static json with data for example:
"headline": { "value": "My page headline" "tag": "h1" }
and I would like to get them later via auto binding:
@Input() headline: IHeadline;
This is what I've done so far:
async loadComponents(data: any) {
const components = await data.reduce(async (components: any, entry: any) => {
const r = await components;
const { component } = entry;
const { alias } = component;
const type: Type<any> = await import(/* webpackChunkName: "[request]", webpackMode: "lazy-once" */ `../${alias}/${alias}.component`).then(module => {
const [ componentType ] = Object.keys(module);
return module[componentType];
}).catch(async error => {
return await import('../noop/noop.component').then(module => module.NoopComponent);
});
r.push({ ...entry, instance: type });
return Promise.resolve(r);
}, Promise.resolve([]));
this.components = components;
this.cdr.detectChanges();
}
@xeladotbe I hope an example below is what you want to do https://stackblitz.com/edit/angular-ivy-ngxd-lazy-resolver-simple-demo?file=src%2Fapp%2Fapp.component.ts
Could you please check it and to say whether it's right for you?
@thekiba that looks really helpful! did you just do that?
@xeladotbe I did it some months ago 🦊
And I know that I have to give more documentation for the NGXD 😅 Hope that I'll todo it soon
the documentation could really use some love. is there a way to set a default in the resolver for types that do not have a specific component? keep up the good work! and can I buy you a coffee? ;)
@xeladotbe If you want to return a default component you have to make it in the resolver, see an example below:
resolve(type: T): Type<R> {
if (exists(type) {
return resolve(type);
} else {
return getDefaultComponent();
}
}
We can just to drink a coffee ☕️ when I'll visit Germany or you'll in Moscow 😉
sounds good :) thank you!
Hello, @ChazUK
If you want to have using ng-content with dynamic components, you have to create
projectableNodes
by self. I'll explain to you what do you need to do.
- You have to create an
<ng-template>
and put an<ng-content>
in it.@Component({ selector: 'app-x-column', template: ` <!-- 👇 place TemplateRef with NgContent here --> <ng-template #templateRef> <ng-content></ng-content> </ng-template> ` }) class XColumnComponent {}
- Then you have to get the
TemplateRef
using by@ViewChild()
decorator.@Component({ selector: 'app-x-column', template: ` <ng-template #templateRef> <ng-content></ng-content> </ng-template> ` }) class XColumnComponent { // 👇 getting access to the TemplateRef @ViewChild('templateRef', { static: true, read: TemplateRef }) templateRef: TemplateRef<{}>; }
- After that you need to create a
projectableNodes
. In order to do this you have to create aViewRef
of theTemplateRef
and getrootNodes
. Don't forget to place theprojectableNodes
in*ngxComponentOutlet="content: projectableNodes"
.@Component({ selector: 'app-x-column', template: ` <ng-template #templateRef> <ng-content></ng-content> </ng-template> <ng-container *ngxComponentOutlet=" <!-- putting 👇 projectableNodes into the content of NgxComponentOutlet --> component; content: projectableNodes"></ng-container> ` }) class XColumnComponent { projectableNodes: any[][]; @ViewChild('templateRef', { static: true, read: TemplateRef }) set templateRef(t: TemplateRef<{}>) { // 👇 creating projectableNodes this.projectableNodes = [ t.createEmbeddedView({}).rootNodes ]; }; }
- Don't forget to destroy the ViewRef, otherwise, this can lead to leaks.
this.viewRef = t.createEmbeddedView({}); // some later this.viewRef.destroy();
Hey @thekiba ,
do you have a complete working example of this? I've tried to integrate your suggestions into the example of ChazUK but I can't get it to work.
What I want:
I've a headless CMS which provides me a JSON, I can render the component without problems, but partly it is allowed to use markdown in a text, now I want to convert the markdown to HTML and output it. The generated HTML can contain for example [ngModel]="..." or [routerLink]="...". directives, I thought with content projection I can display it without problems, but I don't know how.
Pseudo code:
<ng-template #paragraph>
<ng-content></ng-content>
</ng-template>
<ng-container *ngFor="let paragraph of paragraphs">
<ng-container #paragraph>
{{paragraph.value | transformToHTML | safeHTML}}
</ng-container>
</ng-container>
paragraph.value = Hi *there*! [How are you](how-are-you)
transformToHTML = Hi <strong>there</strong! <a [routerLink]="['how-are-you']">How are you</a>
safeHTML = DomSanitizer.bypassSecurityTrustHtml
To render my components I use:
<ng-container *ngFor="let component of components; index as index">
<ng-container *ngxComponentOutlet="component.instance; context: component.data"></ng-container>
</ng-container>
One component in my case would be "MediaTextComponent" with the following definition
export interface MediaTextComponent {
headline: string | undefined;
text: string | undefined;
paragraphs: Array<{
headline: string | undefined;
value: string
}>
}
Thanks in advance for your help!
Regards, Alex
Hey @xeladotbe,
Could you please reproduce an example on the StackBlitz? This will help me to better understand the problem to help you.
Hey @thekiba ,
that was the last thing I tried out of desperation https://stackblitz.com/edit/angular-simple-dynamic-h5rxqr?file=src/app/app.module.ts
Hey @xeladotbe!
You doing it wrong. You should pass component ngxComponentOutlet
instead of templateRef. In your case you shold use
<p *ngFor="let paragraph of paragraphs" [innerHtml]="paragraph?.value | toHTML"></p>
But the [routerLink]
from pipe won't work anyway
Hey @alQlagin ,
thanks! Is there any way to make the routerLinks work?
@xeladotbe this question is out of scope for this issue. In short you can't apply any dicrectives to html content from CMS. But you can handle link click and use Router api. See this example https://stackblitz.com/edit/angular-inner-html-links?file=src/app/app.component.ts
@alQlagin thanks for the example, is this a best practice in angular? are there no standard solutions for this? I thought I could solve the problem with the help of content projection, hence the question what I have to do to get my dynamic html running with the help of content projection and ngxComponentOutlet
@xeladotbe I'm not sure about best practice but it works. Maybe @thekiba could tell his solution.
By the way html from CMS is not dynamic content projection. Content projection works only for compiled code. Possibly you can use JIT, but it's a bad practice
Hi,
I'm looking to use NGXD to load dynamic components and content provided by a headless CMS like Contentful, but I've come into a bit of an issue where I'm struggling to figure out how to use content projection with the dynamically loaded components.
I have an X Column component that can host a number of predefined components, some of which are able to take complex content using
ng-content
. Is this something this package can handle? And is it possible to get an example?Here's some demo code I've created to explain the situation https://stackblitz.com/edit/angular-simple-dynamic-8emvyb?file=src/app/app.module.ts