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.

2

3 Answers 3

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'
  ];

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

10 Comments

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<void>'. What did I miss?
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 :)
Yes sure ;-) You should have access to the properties of the component (not only parameters)...
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.
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 :)
|
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);

2 Comments

loadIntoLocation is no longer a function. See beta 16 changelog.
@self. fixed! added example for latest version (@2.0.0-rc.1)!
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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.