Dynamische Registerkarten mit vom Benutzer angeklickten ausgewählten Komponenten

223

Ich versuche, ein Registerkartensystem einzurichten, mit dem sich Komponenten selbst registrieren können (mit einem Titel). Die erste Registerkarte ist wie ein Posteingang. Für die Benutzer stehen zahlreiche Aktionen / Linkelemente zur Auswahl. Mit jedem dieser Klicks sollte eine neue Komponente beim Klicken instanziiert werden können. Die Aktionen / Links stammen von JSON.

Die instanziierte Komponente registriert sich dann als neue Registerkarte.

Ich bin mir nicht sicher, ob dies der "beste" Ansatz ist. Bisher habe ich nur Anleitungen für statische Registerkarten gesehen, was nicht hilft.

Bisher habe ich nur den Tabs-Dienst, der hauptsächlich gebootet ist, um in der gesamten App zu bestehen. Es sieht ungefähr so ​​aus:

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

Fragen:

  1. Wie kann ich eine dynamische Liste im Posteingang haben, die neue (verschiedene) Registerkarten erstellt? Ich vermute, dass das verwendet werden DynamicComponentBuilderwürde?
  2. Wie können die Komponenten aus dem Posteingang (per Klick) erstellt werden, sich als Registerkarten registrieren und auch angezeigt werden? Ich vermute ng-content, aber ich kann nicht viele Informationen darüber finden, wie man es benutzt

EDIT: Ein Versuch zu klären.

Stellen Sie sich den Posteingang als Posteingang vor. Elemente werden als JSON abgerufen und es werden mehrere Elemente angezeigt. Sobald auf eines der Elemente geklickt wird, wird eine neue Registerkarte mit dem Elementaktionstyp "Elemente" erstellt. Der Typ ist dann eine Komponente.

EDIT 2: Bild .

Kraftstoff
quelle
Wenn die auf den Registerkarten angezeigten Komponenten zum Zeitpunkt der Erstellung nicht bekannt sind, ist DCL der richtige Ansatz.
Günter Zöchbauer
7
Ich verstehe Ihre Anforderung nicht klar, so schwer, Ihnen etwas zu sagen, ohne Code / Plunker zu funktionieren. Schauen Sie, ob es Ihnen irgendwo helfen kann plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview (ich weiß nicht, ob es relevant ist oder nicht)
Mikronyken
@micronyks Ich denke, Sie haben den falschen Link
Cuel
Hallo! Ich versuche zu tun, wonach du gefragt hast. Bisher habe ich es geschafft, die Registerkarte mit dynamischem Inhalt zu erstellen, aber ich habe keine zufriedenstellende Möglichkeit gefunden, den Komponentenstatus beizubehalten, wenn die Registerkarte geändert wird (geladene Komponenten können sehr unterschiedlich sein). Wie hast du es geschafft?
Gipinani

Antworten:

266

aktualisieren

Angular 5 StackBlitz Beispiel

aktualisieren

ngComponentOutlet wurde zu 4.0.0-beta.3 hinzugefügt

aktualisieren

Es ist eine NgComponentOutletArbeit in Arbeit, die etwas Ähnliches tut https://github.com/angular/angular/pull/11235

RC.7

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

Anwendungsbeispiel

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

Siehe auch angle.io DYNAMISCHER KOMPONENTENLADER

ältere Versionen xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Dies hat sich in Angular2 RC.5 erneut geändert

Ich werde das folgende Beispiel aktualisieren, aber es ist der letzte Tag vor dem Urlaub.

Dieses Plunker-Beispiel zeigt, wie Komponenten in RC.5 dynamisch erstellt werden

Update - Verwenden Sie ViewContainerRef .createComponent ()

Da dies DynamicComponentLoaderveraltet ist, muss der Ansatz erneut aktualisiert werden.

@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-Beispiel RC.4
Plunker-Beispiel Beta.17

Update - Verwenden Sie 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 Beispiel Beta.17

Original

Ich bin mir aus Ihrer Frage nicht ganz sicher, was Ihre Anforderungen sind, aber ich denke, dies sollte tun, was Sie wollen.

Die TabsKomponente erhält ein Array von übergebenen Typen und erstellt "Registerkarten" für jedes Element im 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];
}

Plunker-Beispiel Beta.15 (basiert nicht auf Ihrem Plunker)

Es gibt auch eine Möglichkeit, Daten weiterzugeben, die an die dynamisch erstellte Komponente wie übergeben werden können ( someDatamüsste wie übergeben werden type)

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

Es gibt auch Unterstützung für die Verwendung der Abhängigkeitsinjektion mit gemeinsam genutzten Diensten.

Weitere Informationen finden Sie unter https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html

Günter Zöchbauer
quelle
1
Sicher, Sie müssen nur den Komponententyp auf den setzen DclWrapper, damit eine tatsächliche Instanz erstellt wird.
Günter Zöchbauer
1
@ Joseph Sie können injizieren ViewContainerRefanstatt zu verwenden ViewChild, dann wird <dcl-wrapper>selbst das Ziel. Elemente werden als Geschwister des Ziels hinzugefügt und befinden sich daher außerhalb <dcl-wrapper>dieses Weges.
Günter Zöchbauer
1
Das Ersetzen wird nicht unterstützt. Sie können die Vorlage in ''(leere Zeichenfolge) `und den Konstruktor in ändern constructor(private target:ViewContainerRef) {}, dann werden dynamisch hinzugefügte Komponenten zu Geschwistern von<dcl-wrapper>
Günter Zöchbauer
1
Ich benutze RC4 und das Beispiel war sehr nützlich. Das einzige, was ich erwähnen wollte, war, dass ich den folgenden Code hinzufügen musste, damit die Bindung ordnungsgemäß funktioniert. This.cmpRef.changeDetectorRef.detectChanges ();
Rajee
4
Ich habe eine Fehlermeldung erhalten, wenn die dynamische Komponente bei Verwendung von ngAfterViewInit eine andere Dynaimc-Komponente hatte. Stattdessen in ngAfterContentInit geändert und jetzt funktioniert es mit verschachtelten dynamischen Komponenten
Abris
20

Ich bin nicht cool genug für Kommentare. Ich habe den Plunker aus der akzeptierten Antwort repariert, um für rc2 zu arbeiten. Nichts Besonderes, Links zum CDN waren nur kaputt ist alles.

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

davimusprime
quelle
16

es ist gebrauchsfertig Komponente (RC5 kompatibel) NG2 Schritte welche Verwendungen Compilereinzuspritzen Komponente zu Schritt Behälter und Service für die Verdrahtung alles zusammen (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);
    });
  }

}
Neuronet
quelle