7

I'm trying to create a table component which would build up a table from a column description and raw data.

I was able to create the basic functionality without a problem, but I would also like to be able to override the default rendering of a single cell in order to be able to show custom content ( for example, to show links ).

In a similar fashion that is used in DataTables, with the only difference being, that I would like to be able to pass in a name for a custom component with parameters.

My initial attempt was something like this, but the problem arises that if I instantiate the component in the display function of the column, I can't show it on the page.

@Component({
  selector: 'gt-table',
  template: `
  <table class="table table-striped table-hover">
    <thead>
      <tr>
        <th *ngFor="#column of columns">
          {{ column.title }}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="#row of data">
        <td *ngFor="#column of columns">
          {{ displayCell(row, column) }}
        </td>
      </tr>
    </tbody>
  </table>
  `
  })
  export class GTTableComponent implements {
    @Input() data:Array<any>;
    @Input() columns:Array<any>;

  displayCell(row, column) {
    if (column.display !== undefined) {
      return column.display(row[column.name], row);
    } else {
      return row[column.name];
    }
  }
}

After searching on the internet I was able to find out that in order to dynamically load a component in a view you need to use DynamicComponentLoader.

After a lot of headaches and searches I was able to find something similar to what I'm looking for in this response, even though it's not the prettiest.

My only question that remains is how can I pass in parameters? I wasn't able to find a single reference regarding loading components dynamically with parameters. Any help or advise?

Solution:

So the solution is to use DynamicComponentLoader with it's loadIntoLocation function. The loadAsRoot would be more ideal, but as of the state of 2.0.0-beta.7 it's buggy and you aren't able to set the instance property from the promise.

So what I did is create a component for cells in which I decide if I need to instantiate a default display component or the user wants to handle it:

import {Component, Input, OnInit, ElementRef, DynamicComponentLoader, 
ComponentRef} from 'angular2/core';

import {GTTableDefaultDisplayComponent} from './gt-tabledefaultdisplay.component';
@Component({
    selector: '[gt-cell]',
    template: `<div #content></div>`,
    directives: []
})
export class GTTableCellComponent implements OnInit {
  @Input() row:any;
  @Input() column:any;

  constructor(private _loader: DynamicComponentLoader, private _elementRef: ElementRef) {
  }

  ngOnInit() {
    if (this.column.display !== undefined) {
      this.column.display(this._loader, this._elementRef, 'content',
      this.row[this.column.name],this.row);
    } else {
      this._loader.loadIntoLocation(GTTableDefaultDisplayComponent,
      this._elementRef, 'content').then((compRef:ComponentRef) => {
        compRef.instance['content'] = this.row[this.column.name];
      });
    }
  }
}

So the way to load a dynamic component with property is to set it in the promise returned by using compRef:ComponentRef. This will be a variable in which you can access the instance of the newly created component with compRef.instance and use it as an array to set its variables.

My default view component is as simple as this: import {Component, Input} from 'angular2/core';

import {GTTableLinkComponent} from './gt-tablelink.component';

@Component({
    selector: 'default-display',
    template: `<div>{{content}}</div>`,
    directives: []
})
export class GTTableDefaultDisplayComponent {
  @Input() content:string;

  constructor() {
  }
}

I hope this will help others too.

Community
  • 1
  • 1
G.T.
  • 562
  • 8
  • 18
  • 2
    "`DynamicComponentLoader` is deprecated. Use `ComponentResolver` and `ViewContainerRef` directly." https://github.com/angular/angular/commit/5297c9d9ccc6f8831d1656915e3d78e767022517 – jtzero Jul 15 '16 at 16:51
  • See an example for ViewContainerRef.createComponent at http://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468 – Günter Zöchbauer Jul 22 '16 at 16:14

3 Answers3

10

You can use the promise returned by the loadAsRoot (or loadNextToLocation or loadIntoLocation) method to have access to the newly component instance and set elements on it:

dcl.loadAsRoot(ChildComponent, '#child',
   injector).then((compRef:ComponentRef) => {

  // For example
  compRef.param1 = 'something';

  // In your case
  compRef.data = [
    { ... },
    { ... },
    { ... }
  ];
  compRef.columns = [
    'column1', 'column2'
  ];

});
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • I'm trying to give parameters to my GTTableLinkComponent in order to make navigatable links on the site. What I have based on your answer is: `this._loader.loadNextToLocation(GTTableLinkComponent, elementRef).then((compRef:GTTableLinkComponent) => { compRef.routeName = "ProjectInitDetails"; compRef.routeParams = {id: 1}; compRef.routeDisplay = "test"; });` But I get an error that `Argument of type '(compRef: GTTableLinkComponent) => void' is not assignable to parameter of type '(value: ComponentRef) => void | PromiseLike'.` What did I miss? – G.T. Feb 22 '16 at 14:47
  • 1
    Ohh, I missed ComponentRef. But if I add ComponentRef as (compRef:ComponentRef) I can't access the parameters. Let me read up on ComponentRef, give me a minute :) – G.T. Feb 22 '16 at 14:51
  • Yes sure ;-) You should have access to the properties of the component (not only parameters)... – Thierry Templier Feb 22 '16 at 14:53
  • Ok, if I use the following syntax: `compRef.instance['routeName'] = "RouteName"` I can set the instantiated component, but something strange is happening. I get infinite number of insert, which I don't yet know if it's because of bad code of mine or something fishy with the way I'm using. I believe the best is if I create a plunker example. – G.T. Feb 22 '16 at 14:58
  • Ok, it's somewhat working, but I have a problem with the behavior of the DynamicComponentLoader. If I use loadIntoLocation, it will place it as a simbling to the location holder, but I can set the parameters of the instantiated object as intended. If I use the loadAsRoot it will place it as the root object, as intended, but I'm no longer able to use the method of compRef to set the properties. At least no property is set for it. Any idea? – G.T. Feb 23 '16 at 07:44
  • I still don't know why is the loadAsRoot doesn't working as expected, but I was able to do it with the loadIntoLocation function anyway, even though I have some small garbage html in the setup. – G.T. Feb 23 '16 at 08:06
  • In fact there is a bug on this method :-( See this answer: http://stackoverflow.com/questions/35017539/angular-2-dynamiccomponentloader-data-not-binding-to-template/35017607#35017607 – Thierry Templier Feb 23 '16 at 08:08
  • 1
    Well that's the perk of working with beta stuff :) By the way, I believe in the documentation the Promise and related stuff isn't stressed enough :) Thanks for your help. When I'll finish my component and if I can clean it up well enough I'll probably post it on github and mention you as helping hand. Thanks for you help once again :) – G.T. Feb 23 '16 at 08:18
  • Yes, agreed for the promise part ;-) You're welcome! – Thierry Templier Feb 23 '16 at 08:21
  • New correct docs link - https://angular.io/docs/ts/latest/api/core/index/DynamicComponentLoader-class.html – shershen Aug 12 '16 at 10:01
4

DynamicComponentLoader's methods return Promise<ComponentRef>s.

The ComponentRef in the Promise<ComponentRef> has an instance field. Use it to change your newly created component's attributes.

Demo (@2.0.0-rc.4 tested; should work for later versions)

import { Component, DynamicComponentLoader,  ViewContainerRef,
                                                         ViewChild } from '@angular/core';
import { bootstrap } from '@angular/platform-browser-dynamic';

@Component({
  selector: 'loaded-with-var-component',
  template: '~~LOADED COMPONENT WITH VAR: {{ varValue }}~~'
})
class LodadedWithVarComponent {
  varValue: String = "defaultVarValue";
}


@Component({
  selector: 'my-app',
  template: '(<span #myVar></span>)<br>Parent'
})
class MyApp {

  @ViewChild('myVar', {read: ViewContainerRef}) myVar:ViewContainerRef;

  constructor(private dcl: DynamicComponentLoader) {
  }

  ngAfterViewInit() {
    this.dcl.loadNextToLocation(LodadedWithVarComponent, this.myVar)
      .then((c:ComponentRef) => {
        c.instance.varValue = 'changed-var-value';  // <-- This is where the magic happens!
      });
  }
}
bootstrap(MyApp);

And that's it!


Old: previous demo using angular2-beta.15 and below

import {DynamicComponentLoader, ElementRef, Component, Input, View} from 'angular2/core';
import {bootstrap}    from 'angular2/platform/browser';

@Component({
  selector: 'child-component',
  template: 'Child {{ stuff }}'
})
class ChildComponent {
    stuff: String = "defaultValue";
}

@Component({
  selector: 'my-app',
  template: 'Parent (<div #child></div>)'
})
class MyApp {
  constructor(private dcl: DynamicComponentLoader, private elementRef: ElementRef) {
  }
  ngOnInit() {
    this.dcl.loadIntoLocation(ChildComponent, this.elementRef, 'child')
      .then((c:ComponentRef) => {
         c.instance.stuff = 'something'; // <------------ This is where the magic happens!
      });
  }
}

bootstrap(MyApp);
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • 3
    `loadIntoLocation` is no longer a function. See [beta 16 changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md#200-beta16-2016-04-26). – self. May 02 '16 at 17:56
  • 1
    @self. fixed! added example for latest version (**`@2.0.0-rc.1`**)! – acdcjunior May 25 '16 at 20:32
0

using Attribute Directives.

An attribute directive minimally requires building a controller class annotated with a Directive decorator. The Directive decorator specifies the selector identifying the attribute associated with the directive. The controller class implements the desired directive behavior.

<td [column]="column">...</td>
guyoung
  • 129
  • 1