Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 DynamicComponentLoader with parameters

Tags:

angular

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.

like image 976
G.T. Avatar asked Feb 22 '16 14:02

G.T.


2 Answers

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

});
like image 124
Thierry Templier Avatar answered Nov 15 '22 17:11

Thierry Templier


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);
like image 23
acdcjunior Avatar answered Nov 15 '22 17:11

acdcjunior