Các tab động với các thành phần được chọn bởi người dùng


224

Tôi đang cố gắng thiết lập một hệ thống tab cho phép các thành phần tự đăng ký (với một tiêu đề). Tab đầu tiên giống như một hộp thư đến, có rất nhiều hành động / mục liên kết để lựa chọn cho người dùng và mỗi lần nhấp này sẽ có thể khởi tạo một thành phần mới, khi nhấp vào. Các hành động / liên kết đến từ JSON.

Thành phần khởi tạo sau đó sẽ tự đăng ký như một tab mới.

Tôi không chắc đây có phải là cách tiếp cận 'tốt nhất' không? Cho đến nay, các hướng dẫn duy nhất tôi thấy là dành cho các tab tĩnh, không giúp ích được gì.

Cho đến nay, tôi chỉ có dịch vụ tab được khởi động chính để duy trì trong suốt ứng dụng. Nó trông giống như thế này:

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

Câu hỏi:

  1. Làm cách nào tôi có thể có một danh sách động trong hộp thư đến tạo các tab mới (khác nhau)? Tôi là loại đoán DynamicComponentBuildersẽ được sử dụng?
  2. Làm thế nào các thành phần có thể được tạo từ hộp thư đến (nhấp chuột) tự đăng ký dưới dạng các tab và cũng được hiển thị? Tôi đoán ng-content, nhưng tôi không thể tìm thấy nhiều thông tin về cách sử dụng nó

EDIT: Một nỗ lực để làm rõ.

Hãy nghĩ về hộp thư đến như một hộp thư đến. Các mục được tìm nạp dưới dạng JSON và nó hiển thị một số mục. Khi một trong các mục được nhấp, một tab mới sẽ được tạo với mục 'hành động' đó. Các loại sau đó là một thành phần.

EDIT 2: Hình ảnh .


Nếu các thành phần được hiển thị trong các tab không được biết đến khi xây dựng, thì DCL là phương pháp phù hợp.
Günter Zöchbauer

7
Tôi không hiểu rõ yêu cầu của bạn rõ ràng đến nỗi khó nói với bạn bất cứ điều gì mà không làm việc mã / plunker. Nhìn này nếu nó có thể giúp bạn ở đâu đó plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview (Tôi không biết nếu có liên quan hay không của nó)
micronyks

@micronyks Tôi nghĩ bạn đã liên kết sai
Cuel

Chào! Tôi đang cố gắng làm những gì bạn yêu cầu. Cho đến nay tôi đã quản lý để tạo tab có nội dung động nhưng tôi không tìm thấy cách thỏa mãn để duy trì trạng thái thành phần khi tab được thay đổi (các thành phần được tải có thể rất khác nhau). Làm thế nào bạn quản lý nó?
gipinani

Câu trả lời:


267

cập nhật

Ví dụ về góc 5 StackBlitz

cập nhật

ngComponentOutlet đã được thêm vào 4.0.0-beta.3

cập nhật

Có một NgComponentOutletcông việc đang tiến hành thực hiện một cái gì đó tương tự https://github.com/angular/angular/pull/11235

RC.7

Ví dụ 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();
    }    
  }
}

Ví dụ sử dụng

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

Xem thêm angular.io LOADER THÀNH PHẦN NĂNG ĐỘNG

phiên bản cũ hơn xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Điều này đã thay đổi một lần nữa trong Angular2 RC.5

Tôi sẽ cập nhật ví dụ dưới đây nhưng đó là ngày cuối cùng trước kỳ nghỉ.

Ví dụ Plunker này trình bày cách tạo động các thành phần trong RC.5

Cập nhật - sử dụng ViewContainerRef .createComponent ()

Bởi vì DynamicComponentLoaderkhông được dùng nữa, cách tiếp cận cần được cập nhật lại.

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

Ví dụ về Plunker
RC.4 Ví dụ về Plunker beta.17

Cập nhật - sử dụng 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();
    }    
  }
}

Ví dụ về Plunker beta.17

nguyên

Không hoàn toàn chắc chắn từ câu hỏi của bạn những yêu cầu của bạn là gì nhưng tôi nghĩ điều này nên làm những gì bạn muốn.

Thành Tabsphần này nhận được một mảng các loại được thông qua và nó tạo ra các "tab" cho mỗi mục trong mảng.

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

Ví dụ về Plunker beta.15 (không dựa trên Plunker của bạn)

Ngoài ra còn có một cách để truyền dữ liệu theo đó có thể được chuyển đến thành phần được tạo động như thế nào ( someDatasẽ cần phải được truyền như thế nào type)

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

Ngoài ra còn có một số hỗ trợ để sử dụng tiêm phụ thuộc với các dịch vụ chia sẻ.

Để biết thêm chi tiết, hãy xem https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html


1
Chắc chắn, bạn chỉ cần lấy kiểu thành phần DclWrapperđể làm cho nó tạo một thể hiện thực tế.
Günter Zöchbauer

1
@Joseph Bạn có thể tiêm ViewContainerRefthay vì sử dụng ViewChild, sau đó <dcl-wrapper>chính nó trở thành mục tiêu. Các yếu tố được thêm vào như anh em ruột của mục tiêu và do đó sẽ nằm ngoài <dcl-wrapper>cách này.
Günter Zöchbauer

1
Thay thế không được hỗ trợ. Bạn có thể thay đổi mẫu thành ''(chuỗi rỗng) `và thay đổi hàm tạo thành constructor(private target:ViewContainerRef) {}, sau đó các thành phần được thêm động trở thành anh chị em của<dcl-wrapper>
Günter Zöchbauer

1
Tôi đang sử dụng RC4 và ví dụ này khá hữu ích. Điều duy nhất tôi muốn đề cập là tôi đã phải thêm mã bên dưới để liên kết hoạt động đúng cách này.cmpRef.changeDetectorRef.detectChanges ();
Rajee 17/8/2016

4
Tôi đã gặp lỗi khi thành phần động có thành phần dynaimc khác khi sử dụng ngAfterViewInit. Thay đổi thành ngAfterContentInit thay vào đó và bây giờ nó đang hoạt động với các thành phần động được lồng nhau
Abris

20

Tôi không đủ bình luận để bình luận. Tôi đã sửa lỗi plunker từ câu trả lời được chấp nhận để làm việc cho RC2. Không có gì lạ mắt, các liên kết đến CDN đã bị hỏng là tất cả.

'@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ó thành phần sẵn sàng để sử dụng (tương thích với RC5) các bước ng2 sử dụng Compilerđể tiêm thành phần vào bước chứa và dịch vụ để nối mọi thứ lại với nhau (đồng bộ dữ liệu)

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.