0

Inspired by Angular 2 dynamic tabs with user-click chosen components and Passing Input while creating Angular 2 Component dynamically using ComponentResolver I'm trying to go one step further and have not just the tabs get a dynamic component but also the content of be tab be comprised of dynamic components. From the original example I used components C1-C3 for the tabs and want to use C4 and C5 for sections. I'm aware there isn't any styling/functionality for actually tabbing but the structure should be enough to get me on my way. I made this.

//our root app component
import {Component, ComponentRef, Input, ViewContainerRef, ComponentResolver, ViewChild, Injectable, OnInit} from '@angular/core';

@Component({
  selector: 'dcl-wrapper',
  template: `<div #target></div>`
})
export class DclWrapper {
  @ViewChild('target', {read: ViewContainerRef}) target;
  @Input() type;
  cmpRef:ComponentRef;
  private isViewInitialized:boolean = false;

  constructor(private resolver: ComponentResolver) {}

  updateComponent() {
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
    this.resolver.resolveComponent(this.type.type).then((factory:ComponentFactory<any>) => {
      this.cmpRef = this.target.createComponent(factory);
      this.cmpRef.instance.info = this.type;
    });
  }

  ngOnChanges() {
    this.updateComponent();
  }

  ngAfterViewInit() {
    this.isViewInitialized = true;
    this.updateComponent();  
  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }    
  }
}

@Component({
  selector: 'child-dcl-wrapper',
  template: `<div #target></div>`
})
export class ChildDclWrapper {
  @ViewChild('target', {read: ViewContainerRef}) target;
  @Input() type;
  cmpRef:ComponentRef;
  private isViewInitialized:boolean = false;

  constructor(private resolver: ComponentResolver) {}

  updateComponent() {
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
    this.resolver.resolveComponent(this.type.type).then((factory:ComponentFactory<any>) => {
      this.cmpRef = this.target.createComponent(factory);
      this.cmpRef.instance.info = this.type;
    });
  }

  ngOnChanges() {
    this.updateComponent();
  }

  ngAfterViewInit() {
    this.isViewInitialized = true;
    this.updateComponent();  
  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }    
  }
}

@Component({
  selector: 'c1',
  template: `<h2>c1</h2><p>{{info.name}}</p>
            <my-sections [sections]="section"></my-sections>`

})
export class C1 {
}

@Component({
  selector: 'c2',
  template: `<h2>c2</h2><p>{{info.name}}</p>
            <my-sections [sections]="section"></my-sections>`
})
export class C2 {
}

@Component({
  selector: 'c3',
  template: `<h2>c3</h2><p>{{info.name}}</p>
            <my-sections [sections]="section"></my-sections>`

})
export class C3 {
}

@Component({
  selector: 'c4',
  template: `<h2>c4</h2><p>{{info.name}}</p>`

})
export class C4 {
}

@Component({
  selector: 'c5',
  template: `<h2>c5</h2><p>{{info.name}}</p>`

})
export class C5 {
}

@Component({
  selector: 'my-sections',
  directives: [ChildDclWrapper],
  template: `
  <h3>Sections</h3>
  <div *ngFor="let section of type.sections">
    <child-dcl-wrapper [type]="section"></child-dcl-wrapper>
  </div>
`
})
export class Sections {
  @Input() sections;
}

@Component({
  selector: 'my-tabs',
  directives: [DclWrapper],
  template: `
  <h2>Tabs</h2>
  <div *ngFor="let tab of tabs">
    <dcl-wrapper [type]="tab"></dcl-wrapper>
  </div>
`
})
export class Tabs {
  @Input() tabs;
}

@Injectable()
export class AService {
  info = [
    {
      name: "taco",
      type: C1,
      sections: [
        {
          name: "believe",
          type: C4
        },
        {
          name: "car",
          type: C5
        }
      ]
    },
    {
      name: "pete",
      type: C2,
      sections: [
        {
          name: "repeat",
          type: C4
        },
        {
          name: "banana",
          type: C5
        }
      ]
    },
    {
      name: "carl",
      type: C3,
      sections: [
        {
          name: "shotgun",
          type: C4
        },
        {
          name: "helmet",
          type: C5
        }
      ]
    }
  ];

  getServiceInfo() {
    console.log("Bung.");
    return this.info;
  }
}

@Component({
  selector: 'my-app',
  directives: [Tabs],
  providers: [AService],
  template: `
  <h1>Hello {{name}}</h1>
  <my-tabs [tabs]="types"></my-tabs>
`
})
export class App extends OnInit {
  types;
  aService;

  constructor(private aService: AService) {
    this.aService = aService;
  }

  getInfo() {
    console.log("beep");
    this.types = this.aService.getServiceInfo();
  }

  ngOnInit() {
    console.log("boop");
    this.getInfo();
  }
}
2
  • Well crud, just call me Mr. Overcomplicater. Commented Jun 23, 2016 at 14:42
  • @yurzui, throw an answer down below and i'll accept it. Commented Jun 23, 2016 at 14:49

1 Answer 1

2

I see several errors in your code. For example you need to use info.sections instead just section in the following code:

<my-sections [sections]="section"></my-sections>

It should be:

<my-sections [sections]="info.sections"></my-sections>

Also you forgot to add Sections directive like this:

@Component({
  selector: 'c1',
  template: `<h2>c1</h2><p>{{info.name}}</p>
            <my-sections [sections]="info.sections"></my-sections>`,
  directives: [Sections] <== add this line

})
export class C1 {}

And you can use one DclWrapper to do that.

The updated plunkr is here https://plnkr.co/edit/JRyqou9yC6LvSRrCR3eA?p=preview

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.