0

I am trying to dynamically render a string containing HTML markup in Angular. The HTML should be 'Angular compiled', i.e. including data binding and rendering of components (the sort of things I did with $compile in AngularJS).

I have the most part of it working using p3x-angular-compile:

<div [p3x-compile]="Template.Source" [p3x-compile-ctx]="Data"></div>

works as expected and correctly renders Template.Source, i.e.:

this.Template.Source = '<p>Hello</p>';

and also

this.Template.Source = '<p>{{Foo}}</p>';

where Foo is a property on the bound Data object.

However, rendering my self defined angular components doesn't work:

this.Template.Source = '<app-sc-navbar></app-sc-navbar><p>Other arbitrary markup anywhere in string'</p>;

yields an error:

CompileAttribute.js:80

Error: Template parse errors: 'app-sc-navbar' is not a known element: 1. If 'app-sc-navbar' is an Angular component, then verify that it is part of this module. 2. If 'app-sc-navbar' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.

The component (ScNavbarComponent) is part of the declarations of the app module, and - if used in static markup - works fine.

What am I missing here? How can I make the dynamic rendering aware of ScNavbarComponent?

EDIT:

This is the full debugger output:

output

I have tried adding ScNavbarComponent to exports and/or entryComponents.

@NgModule({
  declarations: [
    AppComponent,
    ScNavbarComponent,
    ...
  ],
  imports: [
    BrowserModule,
    ...
    CompileModule
  ],
  entryComponents: [
    ScNavbarComponent
  ],
  providers: [],
  bootstrap: [AppComponent],
  exports: [
    ScNavbarComponent
  ],
})
export class AppModule { }
Marc
  • 12,706
  • 7
  • 61
  • 97

1 Answers1

3

if you want to declare components in one module and use them in another module you need to export them so that you are able to import the module in another module.

In your app.module.ts declare and also export them so that your other module can understand that these are from another module.

 @NgModule({
  imports: [
    BrowserModule
  ],

  declarations: [
    AppComponent,
    ScNavbarComponent
  ],

exports: [
      ScNavbarComponent
],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {

 }

In your other module, you can now import the ScNavbarComponent.

If however you do not have different modules, you may just add the ScNavbarComponent to the entryComponents section of your app.module.ts. If it's not there already you can just add it like below.

entryComponents: [
    ScNavbarComponent
  ]

EDIT:

Something you might consider doing (which might suit your goal better) is using the ComponenFactoryResolver, which allows the dynamic rendering of Angular Components. Below is an example of how this could work:

In your template you could use a template ref like:

<div #navbar></div>

To assign a component to this ref, in your component you should reference this using a ViewChild annotation:

@ViewChild('navbar', { ViewContainerRef }) navBar: ViewContainerRef;

Next you should inject the resolver itself in your constructor:

constructor(private resolver: ComponentFactoryResolver) {}

The resolver is now ready to be used and should be used within the lifecycle hook ngAfterContentInit as described below (make sure your component implements ngAfterContentInit):

ngAfterContentInit() {
  const navBarFactory = this.resolver.resolveComponentFactory(ScNavbarComponent); // make sure to import your component
  const component = this.navBar.createComponent(navBarFactory);
}

After implementing the above code, your ScNavbarComponent should be dynamically projected inside your templateRef.

I hope this helps!

Arwin
  • 181
  • 8
  • Thank you very much! I've tried both approaches, but they don't change the behavior. I've attached more info to the question. Currently, I only have one module, which declares the App and the NavbarComponent. Do have more ideas? ;-) – Marc Jul 03 '18 at 10:44
  • No problem at all! Could you double check the name of the selector of ScNavbarComponent. Are you sure it's "app-sc-navbar"? – Arwin Jul 03 '18 at 14:51
  • I updated my answer with another option you might consider trying out. Let me know if this works for you! – Arwin Jul 03 '18 at 15:05
  • Thank you very much! I've read about `ComponentFactoryResolver` before, but unfortunately it doesn't fit my requirement. I am developing a kind of CMS, where advanced users should be able to edit the rendered markup in an editor, where the markup can contain a couple of predefined directives. As I understand `ComponentFactoryResolver` and your example, it is only suitable to create instances of specific components at runtime. I have not yet found the link to rendering a complete markup string. The example was misleading in my question (only one tag in the markup). I will edit it. THANK YOU. – Marc Jul 03 '18 at 15:14
  • Ah alright, I understand. I stumbled upon this article which seemed to fit your needs: https://gearheart.io/blog/passing-pieces-of-markup-to-components-in-angular-2-and-problems-with-dynamic-content/. But I'm not sure if this will also work with a complete markup string. This technique is also said te be a $compile equivalent. – Arwin Jul 03 '18 at 16:26