Kullanıcı tıklamasıyla seçilen bileşenlere sahip dinamik sekmeler


224

Bileşenlerin kendilerini (bir başlık ile) kaydetmelerini sağlayan bir sekme sistemi kurmaya çalışıyorum. İlk sekme bir gelen kutusu gibidir, kullanıcılar için seçebileceğiniz birçok eylem / bağlantı öğesi vardır ve bu tıklamaların her biri tıklandığında yeni bir bileşen başlatabilmelidir. Eylemler / bağlantılar JSON'dan gelir.

Anlatılan bileşen daha sonra kendisini yeni bir sekme olarak kaydeder.

Bunun 'en iyi' yaklaşım olup olmadığından emin değilim? Şimdiye kadar gördüğüm tek kılavuz statik sekmeler içindi, bu da yardımcı olmuyor.

Şimdiye kadar, sadece uygulama boyunca devam etmek için ana bootstrapped sekme hizmeti var. Şuna benziyor:

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

Sorular:

  1. Gelen kutusunda yeni (farklı) sekmeler oluşturan dinamik bir listeye nasıl sahip olabilirim? Ben bir tür DynamicComponentBuilderolacağını tahmin ediyorum?
  2. Bileşenler gelen kutusundan nasıl oluşturulabilir (tıklandığında) kendilerini sekme olarak kaydedebilir ve ayrıca gösterilebilir mi? Tahmin ediyorum ng-content, ancak nasıl kullanılacağı hakkında fazla bilgi bulamıyorum

EDIT: Açıklığa kavuşturma girişimi.

Gelen kutusunu bir posta gelen kutusu olarak düşünün. Öğeler JSON olarak getirilir ve birkaç öğe görüntüler. Öğelerden biri tıklandığında, bu öğe eylemi 'tür' ile yeni bir sekme oluşturulur. Bu durumda tür bir bileşendir.

EDIT 2: Görüntü .


Sekmelerde gösterilen bileşenler oluşturma zamanında bilinmiyorsa, DCL doğru yaklaşımdır.
Günter Zöchbauer

7
Ihtiyacınızı açıkça anlamak çok zor çalışma kodu / dalgıç olmadan bir şey söylemek. Size bir yerde yardımcı olabilirse buna bakın plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview (Alakalı olup olmadığını bilmiyorum)
micronyks

@micronyks Sanırım yanlış bağlantınız var
Cuel

Selam! İstediğini yapmaya çalışıyorum. Şimdiye kadar sekmeyi dinamik içerikle oluşturmayı başardım, ancak sekme değiştirildiğinde bileşen durumunu devam ettirmenin tatmin edici bir yolunu bulamadım (yüklü bileşenler çok farklı olabilir). Nasıl başardın?
gipinani

Yanıtlar:


267

Güncelleme

Açısal 5 StackBlitz örneği

Güncelleme

ngComponentOutlet 4.0.0-beta.3'e eklendi

Güncelleme

NgComponentOutletBenzer bir şey yapan bir çalışma var https://github.com/angular/angular/pull/11235

RC.7

Plunker örneği 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();
    }    
  }
}

Kullanım örneği

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

Ayrıca bkz. Açısal.io DİNAMİK BİLEŞEN YÜKLEYİCİ

eski sürümler xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Bu, Angular2 RC.5'te tekrar değişti

Aşağıdaki örneği güncelleyeceğim, ancak tatilden önceki son gün.

Bu Plunker örneği , RC.5'te dinamik olarak bileşenlerin nasıl oluşturulacağını gösterir.

Güncelleme - ViewContainerRef .createComponent () öğesini kullanın

Kullanımdan DynamicComponentLoaderkaldırıldığı için yaklaşımın yeniden güncellenmesi gerekiyor.

@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 örneği RC.4
Plunker örneği beta.17

Güncelleme - loadNextToLocation kullanın

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 örneği beta.17

orijinal

Gereksinimlerinizin ne olduğunu tam olarak emin değilim ama bence bu istediğinizi yapmalı.

TabsBileşen geçti türlerini bir dizi alır ve dizide her öğe için "sekme" oluşturur.

@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 örneği beta.15 ( Plunker'ınıza bağlı değil)

Ayrıca, dinamik olarak oluşturulmuş bileşene someDatailetilebilen (aşağıdaki gibi geçirilmesi gerekecek type) verileri iletmenin bir yolu da vardır.

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

Ayrıca, paylaşılan hizmetler ile bağımlılık enjeksiyonunun kullanılması için bazı destek vardır.

Daha fazla ayrıntı için bkz. Https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html


1
Elbette, DclWrappergerçek bir örnek oluşturmak için bileşen türünü almanız yeterlidir .
Günter Zöchbauer

1
@ Joseph Joseph Kullanmak ViewContainerRefyerine enjekte edebilirsiniz ViewChild, o zaman <dcl-wrapper>kendisi hedef olur. Öğeler hedefin kardeşleri olarak eklenir ve bu nedenle <dcl-wrapper>bu yolun dışında olacaktır .
Günter Zöchbauer

1
Değiştirme desteklenmez. Şablonu ''(boş dize) `olarak değiştirebilir ve constructor(private target:ViewContainerRef) {}<dcl-wrapper>
yapıcıyı

1
RC4 kullanıyorum ve örnek oldukça faydalıydı. Bahsetmek istediğim tek şey düzgün çalışmak için bağlama için aşağıdaki kodu eklemek zorunda this.cmpRef.changeDetectorRef.detectChanges ();
Rajee

4
NgAfterViewInit kullanırken dinamik bileşen başka bir dynaimc bileşeni olduğunda bir hata aldım. Bunun yerine ngAfterContentInit olarak değiştirildi ve şimdi iç içe dinamik bileşenlerle çalışıyor
Abris

20

Yorumlar için yeterince havalı değilim. Pistonu rc2 için çalışmak için kabul edilen cevaptan sabitledim. Hiçbir şey fantezi, CDN bağlantıları kırık sadece hepsi.

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

her şeyi birlikte kablolamak için kabı ve servisi adımlamakCompiler için bileşen enjekte etmek için kullanılan kullanıma hazır (rc5 uyumlu) ng2-adımları vardır (veri senkronizasyonu)

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

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.