Tab dinamis dengan klik komponen yang dipilih pengguna

224

Saya mencoba mengatur sistem tab yang memungkinkan komponen untuk mendaftar sendiri (dengan judul). Tab pertama seperti kotak masuk, ada banyak tindakan / item tautan yang dapat dipilih untuk pengguna, dan masing-masing klik ini harus dapat membuat instantiate komponen baru, di klik. Tindakan / tautan datang dari JSON.

Komponen instantiated kemudian akan mendaftarkan dirinya sebagai tab baru.

Saya tidak yakin apakah ini pendekatan 'terbaik'? Sejauh ini, satu-satunya panduan yang saya lihat adalah tab statis, yang tidak membantu.

Sejauh ini, saya hanya punya layanan tab yang bootstrap di main untuk bertahan di seluruh aplikasi. Itu terlihat seperti ini:

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

Pertanyaan:

  1. Bagaimana saya bisa memiliki daftar dinamis di kotak masuk yang membuat tab baru (berbeda)? Saya agak menebak DynamicComponentBuilderakan digunakan?
  2. Bagaimana komponen dibuat dari kotak masuk (klik) mendaftarkan diri sebagai tab dan juga ditampilkan? Saya menduga ng-content, tetapi saya tidak dapat menemukan banyak info tentang cara menggunakannya

EDIT: Upaya untuk mengklarifikasi.

Pikirkan kotak masuk sebagai kotak masuk surat. Item diambil sebagai JSON dan menampilkan beberapa item. Setelah salah satu item diklik, tab baru dibuat dengan 'tipe' aksi item tersebut. Jenis ini kemudian merupakan komponen.

EDIT 2: Gambar .

Cuel
sumber
Jika komponen yang ditunjukkan pada tab tidak diketahui saat membangun, maka DCL adalah pendekatan yang tepat.
Günter Zöchbauer
7
Saya tidak mengerti kebutuhan Anda dengan jelas sehingga sulit memberi tahu Anda apa pun tanpa kode / plunker yang berfungsi. Lihat ini jika dapat membantu Anda di suatu tempat plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview (saya tidak tahu apakah itu relevan atau tidak)
micronyks
@ micronyks Saya pikir Anda mendapat tautan yang salah
Cuel
Hai! Saya mencoba melakukan apa yang Anda minta. Sejauh ini saya berhasil membuat tab dengan konten dinamis tetapi saya tidak menemukan cara yang memuaskan untuk mempertahankan status komponen ketika tab diubah (komponen yang dimuat bisa sangat berbeda). Bagaimana Anda mengaturnya?
gipinani

Jawaban:

267

memperbarui

Contoh 5 Sudut StackBlitz

memperbarui

ngComponentOutlet telah ditambahkan ke 4.0.0-beta.3

memperbarui

Ada NgComponentOutletpekerjaan yang sedang berjalan yang melakukan sesuatu yang mirip https://github.com/angular/angular/pull/11235

RC.7

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

Contoh penggunaan

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

Lihat juga angular.io LOADER KOMPONEN DINAMIS

versi yang lebih lama xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Ini berubah lagi di Angular2 RC.5

Saya akan memperbarui contoh di bawah ini tetapi ini adalah hari terakhir sebelum liburan.

Contoh Plunker ini menunjukkan cara membuat komponen secara dinamis di RC.5

Perbarui - gunakan ViewContainerRef .createComponent ()

Karena DynamicComponentLoadersudah usang, pendekatan perlu diperbarui lagi.

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

Contoh plunker
RC.4 Contoh plunker beta.17

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

Contoh plunker beta.17

asli

Tidak sepenuhnya yakin dari pertanyaan Anda apa persyaratan Anda, tetapi saya pikir ini harus melakukan apa yang Anda inginkan.

The Tabskomponen mendapat berbagai jenis berlalu dan menciptakan "tab" untuk setiap item dalam 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];
}

Contoh plunker beta.15 (tidak didasarkan pada Plunker Anda)

Ada juga cara untuk meneruskan data yang dapat diteruskan ke komponen yang dibuat secara dinamis seperti ( someDataperlu dilewati seperti type)

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

Ada juga beberapa dukungan untuk menggunakan injeksi ketergantungan dengan layanan bersama.

Untuk perincian lebih lanjut, lihat https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html

Günter Zöchbauer
sumber
1
Tentu, Anda hanya perlu mendapatkan tipe komponen ke DclWrapperuntuk membuatnya membuat instance aktual.
Günter Zöchbauer
1
@ Joseph Anda dapat menyuntikkan ViewContainerRefdaripada menggunakan ViewChild, lalu <dcl-wrapper>itu sendiri menjadi target. Elemen ditambahkan sebagai saudara kandung dari target dan karenanya akan berada di luar <dcl-wrapper>dengan cara ini.
Günter Zöchbauer
1
Mengganti tidak didukung. Anda dapat mengubah templat menjadi ''(string kosong) `dan mengubah konstruktor menjadi constructor(private target:ViewContainerRef) {}, lalu komponen yang ditambahkan secara dinamis menjadi saudara kandung<dcl-wrapper>
Günter Zöchbauer
1
Saya menggunakan RC4 dan contohnya cukup berguna. Satu-satunya hal yang ingin saya sebutkan adalah saya harus menambahkan kode di bawah ini untuk mengikat agar berfungsi dengan benar this.cmpRef.changeDetectorRef.detectChanges ();
Rajee
4
Saya mendapat kesalahan ketika komponen dinamis memiliki komponen dynaimc lain saat menggunakan ngAfterViewInit. Alih-alih berubah menjadi ngAfterContentInit dan sekarang ia bekerja dengan komponen dinamis bersarang
Abris
20

Saya tidak cukup keren untuk berkomentar. Saya memperbaiki plunker dari jawaban yang diterima untuk bekerja untuk rc2. Tidak ada yang mewah, tautan ke CDN rusak begitu saja.

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

ada komponen yang siap digunakan (kompatibel rc5) ng2-langkah yang digunakan Compileruntuk menyuntikkan komponen ke kontainer langkah dan layanan untuk menghubungkan semuanya bersama-sama (sinkronisasi data)

    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
sumber