web-dev-qa-db-fra.com

Onglets dynamiques avec le clic de l'utilisateur sur les composants choisis

J'essaie de mettre en place un système de tabulation permettant aux composants de s'inscrire (avec un titre). Le premier onglet est comme une boîte de réception, il y a une multitude d’actions/éléments de liens parmi lesquels choisir, et chacun de ces clics devrait pouvoir instancier un nouveau composant, au 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 la "meilleure" approche? Jusqu'à présent, les seuls guides que j'ai vus concernent les onglets statiques, ce qui n'aide pas.

Jusqu'à présent, je n'ai que le service d'onglets qui est démarré dans le main pour persister dans l'application. Cela 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);
    }
}

Des questions:

  1. Comment puis-je avoir une liste dynamique dans la boîte de réception qui crée de nouveaux onglets? Je devine en quelque sorte que la DynamicComponentBuilder serait utilisée?
  2. Comment les composants peuvent-ils être créés à partir de la boîte de réception (sur un clic) s'inscrire eux-mêmes sous forme d'onglets et être également affichés? Je devine ng-content, mais je ne trouve pas beaucoup d’informations sur son utilisation

EDIT: Une tentative de clarification.

Pensez à la boîte de réception comme une boîte de réception. Les éléments sont récupérés au format JSON et plusieurs éléments sont affichés. Une fois que l'un des éléments est cliqué, un nouvel onglet est créé avec l'action "type" d'éléments. Le type est alors un composant.

EDIT 2: Image .

207
Cuel

update

Angular 5 exemple StackBlitz

update

ngComponentOutlet a été ajouté à 4.0.0-beta.3

update

Il y a un NgComponentOutlet travail en cours qui fait quelque chose de similaire https://github.com/angular/angular/pull/11235

RC.7

exemple Plunker 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 angular.io CHARGEUR DE COMPOSANTS DYNAMIQUE

versions plus anciennes xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Cela a encore changé dans Angular2 RC.5

Je mettrai à jour l'exemple ci-dessous mais c'est le dernier jour avant les vacances.

Cet exemple de Plunker montre comment créer des composants de manière dynamique dans RC.5

Update - utilisez ViewContainerRef . CreateComponent ()

Comme DynamicComponentLoader est obsolète, 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();
    }    
  }
}

exemple Plunker RC.4
Exemple de Plunker beta.17

Update - utilise 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();
    }    
  }
}

Exemple de Plunker beta.17

original

Vous ne savez pas exactement quelles sont vos exigences, mais je pense que cela devrait faire ce que vous voulez.

Le composant Tabs obtient un tableau des 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];
}

Exemple de Plunker beta.15 (non basé sur votre Plunker)

Il existe également un moyen de transmettre des données qui peuvent être transmises au composant créé dynamiquement, comme (someData aurait besoin d'être transmis comme type)

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

L'utilisation de l'injection de dépendance avec des services partagés est également prise en charge.

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

254
Günter Zöchbauer

Je ne suis pas assez cool pour les commentaires. J'ai corrigé le plunker de la réponse acceptée au travail pour rc2. Rien d'extraordinaire, les liens vers le CDN étaient tout simplement cassé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

19
davimusprime

il y a un composant prêt à l'emploi (compatible rc5) ng2-steps qui utilise Compiler pour injecter le composant dans le conteneur et le service pour tout connecter (synchronisation des données)

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

}
16
neuronet