2

The question

Is there a way in Angular2 to create an instance of a Component without actually injecting it? The Components are inserted into a view using ViewContainerRef.insert() or similar somewhere else.

I know there is the DynamicComponentLoader, but it needs a ViewContainerRef to attach instances which it creates by itself. The view does not even exist at that time.

Problem is, i want to create instances of views, configure them (even through custom factories) and add them to any view at some other point.

There is the ViewContainerRef, which is able to insert a ViewRef at an arbitrairy index which is nice so far, but how do i create a ViewRef from a Component which i created without actually inserting it somewhere?

I hope you get my question :P

Update:

An example application would be a carousel like navigation through forms. The views should get created and only the view with the current index should be loaded with the state it had before.

Here is an unfinished example which contains some TODOs marking the key positions (see loadViews() and showView() to get the idea).

import {Component, ViewChild, ViewContainerRef, AfterViewInit} from '@angular/core'

export interface Storable {
  save(transaction: any): void; //transaction is usually a defined type, but this would go too far for this example
}

@Component({
  selector: 'user-form',
  template: `<div>
   User name: <input type="text" [(ngModel)]="uname" /><br />
   Password: <input type="password" [(ngModel)]="passwd" />
  </div>`
})
export class UserForm implements Storable {
  public uname: string = null;
  public passwd: string = null;

  constructor() {

  }

  public save(transaction: any): void {
    //save this.uname via transaction (not important for this example)
    //save this.passwd via transaction (not important for this example)
  }
}

@Component({
  selector: 'address-form',
  template: `<div>
   Street: <input type="text" [(ngModel)]="street" /><br />
   City: <input type="password" [(ngModel)]="city" /><br />
   Postal Code: <input type="password" [(ngModel)]="postalCode" />
  </div>`
})
export class AddressForm implements Storable {
  public street: string = null;
  public city: string = null;
  public postalCode: string = null;

  constructor() {

  }

  public save(transaction: any): void {
    //save this.street via transaction (not important for this example)
    //save this.city via transaction (not important for this example)
    //save this.postalCode via transaction (not important for this example)
  }
}

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Carousel/Wizzard like state maintaining navigation</h2>
      <table style="width: 100%">
        <tr>
          <td style="text-align: left"><button (click)="prevBtnClick()">&lt; Back</button></td>
          <td style="text-align: right"><button (click)="nextBtnClick()">Next &gt;</button></td>
        </tr>
        <tr>
          <td colspan="2" style="text-align: center" #content>
            <!-- here goes the content -->
            <p *ngIf="ci < 0">No content loaded</p>
          </td>
        </tr>
      </table>
    </div>
  `,
  directives: []
})
export class App implements AfterViewInit {
  @ViewChild('content', {read: ViewContainerRef}) content: ViewContainerRef;

  private ci = -1;
  private views = [];

  constructor() {
    this.loadViews();

    if(this.views.length > 0) {
      this.ci = 0;
    }
  }

  public ngAfterViewInit(): void {
    this.showView(this.ci);
  }

  //THIS IS THE KEY PART Nr. 1
  public loadViews(): void { //this is simplyfied, in my case this is happening via a config loader which returns an array of "INITIALIZED" views
    //i know that this does not work! It is just to get the idea:
    var v = new UserForm();
    v.uname = "Some initialized value";
    v.passwd = "Usually loaded by another instance";
    this.views.push(v);

    v = new AddressForm();
    v.street = "Examplestreet 15";
    v.city = "Exampletown";
    v.postalCode = "12345";
    this.views.push(v);
  }

  protected prevBtnClick(): void {
    if(this.ci < 0)
      return;

    this.ci++;

    if(this.ci >= this.views.length)
      this.ci = 0;

    this.showView(this.ci);
  }

  protected nextBtnClick(): void {
    if(this.ci < 0)
      return;

    this.ci--;

    if(this.ci < 0)
      this.ci = this.views.length - 1;

    this.showView(this.ci);
  }

  //THIS IS THE KEY PART Nr. 2
  public showView(index: number): void {
    if(index < 0 || index >= this.views.length || !this.views || this.views.length == 0)
      return;

    this.ci = index;

    var view = this.views[index];

    //TODO: remove the currently loaded view from this.content
    //TODO: load the view instance in this.content without creating a new instance
    //      because the state of the instance must be maintained!!
  }

  //not important for this example
  public save(transaction: any): void {
    for(var v of this.views)
      (<Storable><any>v).save(transaction);
  }
}

In Plunkr: Feel free to update the example

seraph
  • 351
  • 1
  • 3
  • 12
  • I don't see why this would be a problem using `ViewContainerRef`. Sounds similar to http://stackoverflow.com/a/36325468/217408 – Günter Zöchbauer May 23 '16 at 08:40
  • Because this approach is a factory and angular creates the instance for me. If i have for example the views created by a loader or similar which also initializes the views this would not work! Like in my carousel example. I want to pre configure the views (based on a config loader), then i want to display the correct view according to an index. The views must maintain their states! – seraph May 23 '16 at 09:00

0 Answers0