0

I'm currently creating a reusable ngx-datatable wrapper component, which will be used throughout my application, as every table is different, I'll need multiple ng-templates to accomodate each component's needs.

The idea I was working on was creating a SharedTemplates component, which would hold a number of ng-templates, and expose them by simply having them as public properties, such as:

@ViewChild("yesNoTemplate") public yesNoTemplate: TemplateRef<any>;

So that I could use them later as:

tableComponents: TableComponents = new TableComponents();

     `{
        name: 'Aprobado',
        prop: 'aprobado',
        cellTemplate: this.tableComponents.yesNoTemplate
      }`

However this doens't seem to be working, tableComponents doesn't have any properties, I assume because it's not being rendered (?).

Using the component itself as the template doesn't work, as I get the error message:

TypeError: templateRef.createEmbeddedView is not a function

How could I store these templates to be reused a number of times?

Danyx
  • 574
  • 7
  • 34
  • Can you provide the relevant code you currently have - the minimum it would take to create a table using a template for the `cellTemplate` (obviously not via the final approach you want to take). – Kurt Hamilton Mar 13 '20 at 16:50

5 Answers5

0

That error may be caused by using deprecated template.

So you should use ng-template instead of template: https://angular.io/guide/angular-compiler-options#enablelegacytemplate

StackBlitz example

adrisons
  • 3,443
  • 3
  • 32
  • 48
  • I'm using ng-template, the problem I'm facing isn't getting a template that's within the same component, but taking a template from a different component. – Danyx Mar 16 '20 at 20:28
  • Ah! I understand. You want to insert a component dynamically. Maybe this helps: https://angular.io/guide/dynamic-component-loader – adrisons Mar 17 '20 at 09:54
0

I don't know which angular version you are using, but I had a similar issue.

In my case I'm using Angular 9, and I was trying to create a component where a template had to be loaded in which contained html but also interpolation strings "{{}}" and styles had to be applied to it aswell. So both the template and the style came from the server.

While being on my quest I have also tried to ask this question on stackoverflow: Dynamically inject HTML and CSS on request.

At some point I started asking questions in coding communities on Discord, there someone gave me a link to github.

Thanks to the link I received I was able to achieve what I needed:

"Loading templates on the fly based off of a request."

This was achieved thanks to alarm9k's post.

If it wasn't for that post, I probably would be in the same situation as you right now.

I hope that this is kinda what you needed.

Oh and for building tables (with sorting, filtering, paging, ...etc.) I used Clarity Design System's Datagrid, dunno if it is interesting in your case or not.

WARNING:

I do have to mention that it comes with 1 bad effect. You cannot build the project in production mode as it will not contain the angular compiler which is necessarry for this solution. I'm trying to solve this issue myself, mostly because the app size increase dramatically (in my case from 2mb to around 40-45mb).

Using both AOT and JIT in production within an Angular 9 app

Community
  • 1
  • 1
Billy Cottrell
  • 443
  • 5
  • 20
0

You should instantiate the SharedTemplatescomponent at the root level then store all the ng-template instances to a shared service.

Here is my demo on StackBlitz: https://stackblitz.com/edit/angular-s5ffjr

0

Recommend creating components for each template required, and later resolving them with 'ComponentFactoryResolver' and attaching to the host component.

Offers greater extensibility.

let type = YesNoComponent;
let factory = cfr.resolveComponentFactory(type);
let instance = host.viewContainerRef.createComponent(factory);

ComponentFactoryResolver

ViewContainerRef

0

What you are trying to do is creating components programmatically and then access their methods and properties.

The way Angular works for creating an instance of a component is using a Factory resolver which will create the new component, and a ViewContainerRef where the component will be attached and rendered.

First create a directive which will serve as the ViewContainerRef for one of your tables, which will render the component (what I believe you are missing right now) and allow you to access its methods and properties.

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appTable]'
})
export class TableDirective {

  constructor(public viewContainerRef: ViewContainerRef) { }

}

Now you can add it to a ng-template tag and render a component inside using the FactoryResolver:


import { Component, ComponentFactoryResolver, ViewChild, OnInit, ComponentRef } from '@angular/core';
import { TableComponent } from './table/table.component';
import { TableDirective } from './table.directive';

@Component({
  selector: 'app-root',
  template: `
  <ng-template appTable></ng-template>
  `,
})
export class AppComponent implements OnInit {

  @ViewChild(TableDirective) tableHost: TableDirective;
  tableComponent: ComponentRef<TableComponent>;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {

  }

    ngOnInit() {

    // TableComponents = new TableComponents();  wrong way!

    /**
    * creating the component factory resolver
    */
    const tableFactory = this.componentFactoryResolver.resolveComponentFactory(TableComponent);
    const viewContainerRef = this.tableHost.viewContainerRef;

    /**
     * clearing just in case something else was rendered before
     */
    viewContainerRef.clear();

    /**
     * now you will be able to access the methods and properties of your component after this
     */
    this.tableComponent = viewContainerRef.createComponent(tableFactory);
  }
}

Finally as the component is being rendered programmatically you need to add it in the entryComponents array in you module:

@NgModule({
  declarations: [
    AppComponent,
    TableComponent,
    TableDirective
  ],
  imports: [
    BrowserModule
  ],
  entryComponents: [TableComponent],
  providers: [],
  bootstrap: [AppComponent]
})

I made a demo so it's more clear, you can read more about creating components dynamically in this article by Luka Onikadze.