Onglets dynamiques angulaires avec les composants choisis par l'utilisateur-click

j'essaie de configurer un système d'onglets qui permet aux composants de s'enregistrer (avec un titre). Le premier onglet est comme une boîte de réception, Il ya beaucoup d'actions / éléments de lien à choisir pour les utilisateurs, et chacun de ces clics devrait être en mesure d'instancier un nouveau composant, sur le clic. Les actions / liens proviennent de JSON.

le composant instancié s'enregistrera alors comme un nouvel onglet.

Je ne suis pas sûr que ce soit le "meilleur" approche? Sofar les seuls guides que j'ai vu sont pour les onglets statiques, ce qui n'aide pas.

Jusqu'à présent, je n'ai que le service tabs qui est bootstrapped dans la main pour persister tout au long de l'application, ressemble à quelque chose comme ça.

export interface ITab { title: string; }

@Injectable()
export class TabsService {
    private tabs = new Set<ITab>();

    addTab(title: string): ITab {
        let tab: ITab = { title };
        this.tabs.add(tab);
        return tab;
    }

    removeTab(tab: ITab) {
        this.tabs.delete(tab);
    }
}

Questions:

  1. Comment puis-je avoir une liste dynamique dans la boîte de réception qui crée de nouveaux onglets (différents)? Je pense que le DynamicComponentBuilder serait utilisé?
  2. How peut les composants créés à partir de la boîte de réception (clic) s'inscrire eux-mêmes comme des onglets et aussi être montré? Je devine ng-content , mais je ne peux pas trouver beaucoup d'informations sur la façon de l'utiliser

Edit: Essayer de clarifier

pensez à la boîte de réception comme une boîte aux lettres, les éléments sont récupérés comme JSON et affiche plusieurs éléments. Une fois qu'un des éléments est cliqué, un nouvel onglet est créé avec l'action 'type'des éléments. Le type est alors un composant

Modifier 2: Image

http://i.imgur.com/yzfMOXJ.png

170
demandé sur zx485 2016-03-31 08:53:43

3 réponses

https://github.com/angular/angular/pull/11235

RC.7

Plunker exemple RC.7

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

  constructor(private componentFactoryResolver: ComponentFactoryResolver, private compiler: Compiler) {}

  updateComponent() {
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      // when the `type` input changes we destroy a previously 
      // created component before creating the new one
      this.cmpRef.destroy();
    }

    let factory = this.componentFactoryResolver.resolveComponentFactory(this.type);
    this.cmpRef = this.target.createComponent(factory)
    // to access the created instance use
    // this.compRef.instance.someProperty = 'someValue';
    // this.compRef.instance.someOutput.subscribe(val => doSomething());
  }

  ngOnChanges() {
    this.updateComponent();
  }

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

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

exemple d'utilisation

// Use dcl-wrapper component
@Component({
  selector: 'my-tabs',
  template: `
  <h2>Tabs</h2>
  <div *ngFor="let tab of tabs">
    <dcl-wrapper [type]="tab"></dcl-wrapper>
  </div>
`
})
export class Tabs {
  @Input() tabs;
}
@Component({
  selector: 'my-app',
  template: `
  <h2>Hello {{name}}</h2>
  <my-tabs [tabs]="types"></my-tabs>
`
})
export class App {
  // The list of components to create tabs from
  types = [C3, C1, C2, C3, C3, C1, C1];
}
@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, DclWrapper, Tabs, C1, C2, C3],
  entryComponents: [C1, C2, C3],
  bootstrap: [ App ]
})
export class AppModule {}

Voir aussi angulaire.IO DYNAMIC COMPONENT LOADER

versions plus anciennes xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

cela a changé à nouveau dans Angular2 RC.5

je vais mettre à jour l'exemple ci-dessous, mais c'est le dernier jour avant les vacances.

ce exemple de plongeur montre comment créer dynamiquement des composants dans RC.5

mise à Jour - ViewContainerRef .createComponent()

parce que DynamicComponentLoader est déprécié, l'approche doit être mise à jour à nouveau.

@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).then((factory:ComponentFactory<any>) => {
      this.cmpRef = this.target.createComponent(factory)
      // to access the created instance use
      // this.compRef.instance.someProperty = 'someValue';
      // this.compRef.instance.someOutput.subscribe(val => doSomething());
    });
  }

  ngOnChanges() {
    this.updateComponent();
  }

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

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

Plunker exemple RC.4

Plunker exemple beta.17

mise à Jour - loadNextToLocation

export class DclWrapper {
  @ViewChild('target', {read: ViewContainerRef}) target;
  @Input() type;
  cmpRef:ComponentRef;
  private isViewInitialized:boolean = false;

  constructor(private dcl:DynamicComponentLoader) {}

  updateComponent() {
    // should be executed every time `type` changes but not before `ngAfterViewInit()` was called 
    // to have `target` initialized
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
    this.dcl.loadNextToLocation(this.type, this.target).then((cmpRef) => {
      this.cmpRef = cmpRef;
    });
  }

  ngOnChanges() {
    this.updateComponent();
  }

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

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

Plunker exemple bêta.17

original

pas tout à fait sûr de votre question Quelles sont vos exigences, mais je pense que cela devrait faire ce que vous voulez.

le composant Tabs reçoit un tableau de types passés et crée des "onglets" pour chaque élément du tableau.

@Component({
  selector: 'dcl-wrapper',
  template: `<div #target></div>`
})
export class DclWrapper {
  constructor(private elRef:ElementRef, private dcl:DynamicComponentLoader) {}
  @Input() type;

  ngOnChanges() {
    if(this.cmpRef) {
      this.cmpRef.dispose();
    }
    this.dcl.loadIntoLocation(this.type, this.elRef, 'target').then((cmpRef) => {
      this.cmpRef = cmpRef;
    });
  }
}

@Component({
  selector: 'c1',
  template: `<h2>c1</h2>`

})
export class C1 {
}

@Component({
  selector: 'c2',
  template: `<h2>c2</h2>`

})
export class C2 {
}

@Component({
  selector: 'c3',
  template: `<h2>c3</h2>`

})
export class C3 {
}

@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;
}


@Component({
  selector: 'my-app',
  directives: [Tabs]
  template: `
  <h2>Hello {{name}}</h2>
  <my-tabs [tabs]="types"></my-tabs>
`
})
export class App {
  types = [C3, C1, C2, C3, C3, C1, C1];
}

Plunker exemple bêta.15 (non basé sur votre Piston)

il y a aussi un moyen de transmettre des données qui peuvent être passées au composant créé dynamiquement comme ( someData devrait être passé comme type )

    this.dcl.loadIntoLocation(this.type, this.elRef, 'target').then((cmpRef) => {
  cmpRef.instance.someProperty = someData;
  this.cmpRef = cmpRef;
});

il existe également un certain soutien pour utiliser l'injection de dépendances avec des services partagés.

pour plus de détails, voir https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html

222
répondu Günter Zöchbauer 2018-02-26 07:16:52

Je ne suis pas assez cool pour les commentaires. J'ai fixé le plongeur de la réponse acceptée pour travailler pour rc2. Rien de fantaisiste, les liens avec le CDN ont été brisés, c'est tout.

'@angular/core': {
  main: 'bundles/core.umd.js',
  defaultExtension: 'js'
},
'@angular/compiler': {
  main: 'bundles/compiler.umd.js',
  defaultExtension: 'js'
},
'@angular/common': {
  main: 'bundles/common.umd.js',
  defaultExtension: 'js'
},
'@angular/platform-browser-dynamic': {
  main: 'bundles/platform-browser-dynamic.umd.js',
  defaultExtension: 'js'
},
'@angular/platform-browser': {
  main: 'bundles/platform-browser.umd.js',
  defaultExtension: 'js'
},

https://plnkr.co/edit/kVJvI1vkzrLZJeRFsZuv?p=preview

15
répondu davimusprime 2016-06-20 20:52:12

il y a un composant prêt à l'emploi (compatible rc5)) ng2-étapes qui utilise Compiler pour injecter un composant dans un récipient step et le service pour le câblage tout ensemble (Data sync)

    import { Directive , Input, OnInit, Compiler , ViewContainerRef } from '@angular/core';

import { StepsService } from './ng2-steps';

@Directive({
  selector:'[ng2-step]'
})
export class StepDirective implements OnInit{

  @Input('content') content:any;
  @Input('index') index:string;
  public instance;

  constructor(
    private compiler:Compiler,
    private viewContainerRef:ViewContainerRef,
    private sds:StepsService
  ){}

  ngOnInit(){
    //Magic!
    this.compiler.compileComponentAsync(this.content).then((cmpFactory)=>{
      const injector = this.viewContainerRef.injector;
      this.viewContainerRef.createComponent(cmpFactory, 0,  injector);
    });
  }

}
14
répondu neuronet 2016-08-10 15:58:54