Schede dinamiche con i componenti scelti facendo clic sull'utente


224

Sto cercando di impostare un sistema a schede che consenta ai componenti di registrarsi (con un titolo). La prima scheda è come una casella di posta, ci sono molte azioni / elementi di collegamento tra cui scegliere per gli utenti e ognuno di questi clic dovrebbe essere in grado di creare un'istanza di un nuovo componente, facendo clic. Le azioni / i collegamenti provengono da JSON.

Il componente istanziato si registrerà quindi come una nuova scheda.

Non sono sicuro che questo sia l'approccio "migliore"? Finora, le uniche guide che ho visto sono per le schede statiche, il che non aiuta.

Finora, ho solo il servizio di schede che è avviato in main per persistere in tutta l'app. Sembra qualcosa del genere:

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

Domande:

  1. Come posso avere un elenco dinamico nella posta in arrivo che crea nuove (diverse) schede? Immagino che DynamicComponentBuildersarebbe usato?
  2. Come è possibile creare i componenti dalla posta in arrivo (al clic) registrarsi come schede e anche mostrarli? Immagino ng-content, ma non riesco a trovare molte informazioni su come usarlo

EDIT: un tentativo di chiarire.

Pensa alla casella di posta come a una casella di posta. Gli elementi vengono recuperati come JSON e vengono visualizzati diversi elementi. Dopo aver fatto clic su uno degli elementi, viene creata una nuova scheda con l'azione "tipo" di quegli elementi. Il tipo è quindi un componente.

EDIT 2: Immagine .


Se i componenti mostrati nelle schede non sono noti al momento della creazione, DCL è l'approccio giusto.
Günter Zöchbauer,

7
Non capisco chiaramente le tue esigenze, così difficile dirti qualcosa senza un codice / plunker funzionante. Guarda questo se può aiutarti da qualche parte plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview (non so se sia pertinente o meno)
micronyks

@micronyks Penso che tu abbia il link sbagliato
Cuel

Ciao! Sto cercando di fare quello che hai chiesto. Finora sono riuscito a creare la scheda con contenuto dinamico ma non ho trovato un modo soddisfacente per mantenere lo stato del componente quando la scheda viene modificata (i componenti caricati possono essere molto diversi). Come ci sei riuscito?
gipinani,

Risposte:


267

aggiornare

Esempio di StackBlitz angolare 5

aggiornare

ngComponentOutlet è stato aggiunto a 4.0.0-beta.3

aggiornare

C'è un NgComponentOutletlavoro in corso che fa qualcosa di simile https://github.com/angular/angular/pull/11235

RC.7

Esempio di 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();
    }    
  }
}

Esempio di utilizzo

// 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 {}

Vedi anche LOADER COMPONENTE DINAMICO angular.io

versioni precedenti xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Questo è cambiato di nuovo in Angular2 RC.5

Aggiornerò l'esempio qui sotto ma è l'ultimo giorno prima delle vacanze.

Questo esempio di Plunker mostra come creare dinamicamente componenti in RC.5

Aggiornamento - uso ViewContainerRef .createComponent ()

Poiché DynamicComponentLoaderè obsoleto, è necessario aggiornare nuovamente l'approccio.

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

Esempio di Plunker RC.4
Esempio di Plunker beta.17

Aggiornamento: utilizzare 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();
    }    
  }
}

Esempio di Plunker beta.17

originale

Non sono del tutto sicuro della tua domanda quali siano i tuoi requisiti, ma penso che questo dovrebbe fare quello che vuoi.

Il Tabscomponente ottiene un array di tipi passati e crea "schede" per ogni elemento dell'array.

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

Esempio di Plunker beta.15 (non basato sul tuo Plunker)

C'è anche un modo per passare i dati che possono essere passati al componente creato dinamicamente come ( someDatadovrebbe essere passato come type)

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

C'è anche un po 'di supporto per usare l'iniezione delle dipendenze con i servizi condivisi.

Per maggiori dettagli consultare https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html


1
Certo, devi solo ottenere il tipo di componenti DclWrapperper farlo creare un'istanza reale.
Günter Zöchbauer,

1
@Joseph Puoi iniettare ViewContainerRefinvece di usare ViewChild, quindi <dcl-wrapper>diventa esso stesso il bersaglio. Gli elementi vengono aggiunti come fratelli del bersaglio e saranno quindi al di fuori di <dcl-wrapper>questo modo.
Günter Zöchbauer,

1
La sostituzione non è supportata. Puoi cambiare il modello in ''(stringa vuota) `e cambiare il costruttore in constructor(private target:ViewContainerRef) {}, quindi i componenti aggiunti dinamicamente diventano fratelli di<dcl-wrapper>
Günter Zöchbauer

1
Sto usando RC4 e l'esempio è stato abbastanza utile. L'unica cosa che volevo menzionare è che ho dovuto aggiungere il codice seguente per il binding per funzionare correttamente this.cmpRef.changeDetectorRef.detectChanges ();
Rajee,

4
Ho ricevuto un errore quando il componente dinamico aveva un altro componente dynaimc quando si utilizza ngAfterViewInit. Modificato invece in ngAfterContentInit e ora funziona con componenti dinamici nidificati
Abris,

20

Non sono abbastanza figo per i commenti. Ho corretto il plunker dalla risposta accettata al lavoro per rc2. Niente di speciale, i collegamenti alla CDN erano interrotti, tutto qui.

'@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


16

c'è un componente pronto per l'uso (compatibile con rc5) ng2-steps che utilizza Compilerper iniettare il componente nel contenitore di passaggi e per il servizio per il cablaggio di tutti i componenti (sincronizzazione dati)

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.