แท็บแบบไดนามิกพร้อมส่วนประกอบที่ผู้ใช้คลิกเลือก


224

ฉันกำลังพยายามตั้งค่าระบบแท็บที่อนุญาตให้ส่วนประกอบลงทะเบียนตัวเอง (พร้อมชื่อ) แท็บแรกเป็นเหมือนกล่องจดหมายมีรายการแอ็คชั่น / ลิงก์มากมายให้เลือกสำหรับผู้ใช้และการคลิกแต่ละรายการเหล่านี้ควรจะสามารถสร้างอินสแตนซ์ของส่วนประกอบใหม่ได้เมื่อคลิก แอ็คชัน / ลิงก์มาจาก JSON

ส่วนประกอบที่สร้างอินสแตนซ์จะลงทะเบียนตัวเองเป็นแท็บใหม่

ฉันไม่แน่ใจว่านี่เป็นวิธีที่ดีที่สุดหรือไม่? จนถึงตอนนี้คำแนะนำเดียวที่ฉันเห็นสำหรับแท็บแบบคงที่ซึ่งไม่ได้ช่วย

จนถึงตอนนี้ฉันเพิ่งได้รับบริการแท็บที่ bootstrapped ในหลักเพื่อคงอยู่ตลอดทั้งแอพ ดูเหมือนว่า:

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

คำถาม:

  1. ฉันจะมีรายการแบบไดนามิกในกล่องจดหมายที่สร้างแท็บใหม่ (แตกต่างกัน) ได้อย่างไร ฉันคาดเดาสิ่งที่DynamicComponentBuilderจะใช้?
  2. ส่วนประกอบจะถูกสร้างขึ้นจากกล่องจดหมาย (เมื่อคลิก) ลงทะเบียนตัวเองเป็นแท็บและยังจะแสดงได้อย่างไร? ฉันคาดเดาng-contentแต่ฉันไม่สามารถหาข้อมูลได้มากนักเกี่ยวกับวิธีใช้งาน

แก้ไข:ความพยายามที่จะชี้แจง

คิดว่ากล่องจดหมายเข้าเป็นกล่องจดหมายจดหมาย รายการถูกดึงมาเป็น JSON และมันแสดงหลายรายการ เมื่อคลิกรายการใดรายการหนึ่งแล้วแท็บใหม่จะถูกสร้างขึ้นพร้อมกับการกระทำของรายการนั้น ๆ ประเภทนั้นเป็นส่วนประกอบ

แก้ไข 2: ภาพ


หากองค์ประกอบที่แสดงในแท็บไม่เป็นที่รู้จัก ณ เวลาการสร้าง DCL เป็นวิธีการที่เหมาะสม
GünterZöchbauer

7
ฉันไม่เข้าใจความต้องการของคุณอย่างชัดเจนดังนั้นบอกอะไรคุณไม่ได้เลย ดูสิ่งนี้หากสามารถช่วยคุณได้ที่ไหนสักแห่งplnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview (ฉันไม่รู้ว่ามันเกี่ยวข้องหรือไม่)
micronyks

@micronyks ฉันคิดว่าคุณผิดลิงค์
Cuel

Hi! ฉันพยายามทำสิ่งที่คุณขอ จนถึงตอนนี้ฉันจัดการเพื่อสร้างแท็บที่มีเนื้อหาแบบไดนามิก แต่ฉันไม่พบวิธีที่น่าพอใจในการคงสถานะส่วนประกอบเมื่อแท็บมีการเปลี่ยนแปลง (ส่วนประกอบที่โหลดอาจแตกต่างกันมาก) คุณจัดการได้อย่างไร
gipinani

คำตอบ:


267

ปรับปรุง

ตัวอย่าง Angular 5 StackBlitz

ปรับปรุง

ngComponentOutlet ถูกเพิ่มใน 4.0.0-beta.3

ปรับปรุง

มีNgComponentOutletความคืบหน้าในการทำงานที่ทำสิ่งที่คล้ายกันhttps://github.com/angular/angular/pull/11235

RC.7

ตัวอย่าง 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();
    }    
  }
}

ตัวอย่างการใช้งาน

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

ดูเพิ่มเติมที่angular.io ตัวโหลดคอมโพเนนต์แบบไดนามิก

รุ่นที่เก่ากว่า xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ที่เก่ากว่า

สิ่งนี้เปลี่ยนไปอีกครั้งใน Angular2 RC.5

ฉันจะอัปเดตตัวอย่างด้านล่าง แต่เป็นวันสุดท้ายก่อนวันหยุด

ตัวอย่าง Plunkerนี้สาธิตวิธีสร้างส่วนประกอบแบบไดนามิกใน RC.5

อัปเดต - ใช้ViewContainerRef .createComponent ()

เนื่องจากDynamicComponentLoaderเลิกใช้แล้วจึงต้องอัปเดตวิธีการอีกครั้ง

@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
RC.4 ตัวอย่างของ Plunker beta.17

อัปเดต - ใช้ 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 beta.17

เป็นต้นฉบับ

ไม่แน่ใจทั้งหมดจากคำถามของคุณว่าความต้องการของคุณเป็นอย่างไร แต่ฉันคิดว่าสิ่งนี้ควรทำในสิ่งที่คุณต้องการ

Tabsองค์ประกอบได้รับอาร์เรย์ของชนิดผ่านและจะสร้าง "แท็บ" สำหรับแต่ละรายการในอาร์เรย์

@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 beta.15 (ไม่ขึ้นอยู่กับ Plunker ของคุณ)

นอกจากนี้ยังมีวิธีการส่งผ่านข้อมูลตามที่สามารถส่งผ่านไปยังองค์ประกอบที่สร้างขึ้นแบบไดนามิกเช่น ( someDataจะต้องผ่านเช่นtype)

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

นอกจากนี้ยังมีการสนับสนุนให้ใช้การฉีดพึ่งพากับบริการที่ใช้ร่วมกัน

สำหรับรายละเอียดเพิ่มเติมโปรดดูhttps://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html


1
แน่นอนว่าคุณเพียงแค่ต้องได้รับประเภทส่วนประกอบที่DclWrapperจะทำให้มันสร้างตัวอย่างที่เกิดขึ้นจริง
GünterZöchbauer

1
@Joseph คุณสามารถฉีดViewContainerRefแทนการใช้ViewChildแล้ว<dcl-wrapper>ตัวเองกลายเป็นเป้าหมาย องค์ประกอบจะถูกเพิ่มเป็นพี่น้องของเป้าหมายและจะอยู่นอก<dcl-wrapper>แนวทางนี้
GünterZöchbauer

1
ไม่รองรับการเปลี่ยน คุณสามารถเปลี่ยนเทมเพลตเป็น''(สตริงว่าง) `และเปลี่ยนคอนสตรัคconstructor(private target:ViewContainerRef) {}เตอร์จากนั้นส่วนประกอบที่เพิ่มเข้ามาจะกลายเป็นพี่น้องของ<dcl-wrapper>
GünterZöchbauer

1
ฉันใช้ RC4 และตัวอย่างมีประโยชน์มาก สิ่งเดียวที่ฉันต้องการพูดถึงคือฉันต้องเพิ่มรหัสด้านล่างสำหรับผูกเพื่อทำงานอย่างถูกต้อง this.cmpRef.changeDetectorRef.detectChanges ();
Rajee

4
ฉันพบข้อผิดพลาดเมื่อองค์ประกอบแบบไดนามิกมีส่วนประกอบ dynaimc อื่นเมื่อใช้ ngAfterViewInit เปลี่ยนเป็น ngAfterContentInit แทนและตอนนี้ทำงานกับส่วนประกอบแบบไดนามิกที่ซ้อนกัน
Abris

20

ฉันไม่เจ๋งพอสำหรับความคิดเห็น ฉันแก้ไขตัวขุดเสียงพลั่วจากคำตอบที่ยอมรับให้ทำงานกับ rc2 ไม่มีอะไรแฟนซีลิงค์ไปยัง CDN ก็พังแค่นั้นแหละ

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

มีส่วนประกอบที่พร้อมใช้งาน (เข้ากันได้กับ rc5) ng2-steps ซึ่งใช้Compilerในการฉีดส่วนประกอบไปยังคอนเทนเนอร์ขั้นตอนและบริการสำหรับการเดินสายทุกอย่างเข้าด้วยกัน (ซิงค์ข้อมูล)

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

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.