ユーザーが選択したコンポーネントをクリックする動的タブ


224

コンポーネントを(タイトル付きで)登録できるタブシステムをセットアップしようとしています。最初のタブは受信トレイのようなもので、ユーザーが選択できるアクション/リンク項目が豊富にあり、クリックするたびに新しいコンポーネントをインスタンス化できます。アクション/リンクはJSONから取得されます。

インスタンス化されたコンポーネントは、それ自体を新しいタブとして登録します。

これが「最良の」アプローチかどうかはわかりませんか?これまでのところ、私が見た唯一のガイドは静的タブに関するものであり、役に立たない。

これまでのところ、アプリ全体で永続化するためにメインでブートストラップされるタブサービスしか持っていません。次のようになります。

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としてフェッチされ、いくつかのアイテムを表示します。項目の1つがクリックされると、その項目のアクション「タイプ」を使用して新しいタブが作成されます。その場合、タイプはコンポーネントになります。

編集2: 画像


タブに表示されるコンポーネントがビルド時に分からない場合は、DCLが適切なアプローチです。
ギュンターZöchbauer

7
私はあなたの要件を明確に理解していませんので、コード/プランカーを機能させずに何かを難しいことを伝えます それはどこかにあなたを助けることができる場合は、これを見てplnkr.co/edit/Ud1x10xee7​​BmtUaSAA2R?p=preview(その関連するかどうかは知りません)
micronyks

私はあなたが間違ったリンクだと思う@micronyks
CUEL

こんにちは!私はあなたが要求したことをしようとしています。これまでのところ、動的コンテンツを使用してタブを作成することができましたが、タブが変更されたときにコンポーネントの状態を維持するための満足できる方法が見つかりませんでした(ロードされたコンポーネントは非常に異なる場合があります)。どのように管理しましたか?
gipinani

回答:


267

更新

Angular 5 StackBlitzの例

更新

ngComponentOutlet 4.0.0-beta.3に追加されました

更新

NgComponentOutlet同様の何かを行う進行中の作業がありますhttps://github.com/angular/angular/pull/11235

RC.7

プランカーの例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も参照してくださいDYNAMIC COMPONENT LOADER

古いバージョン xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

これは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();
    }    
  }
}

プランカーの例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して、実際のインスタンスを作成する必要があります。
ギュンターZöchbauer

1
@Joseph ViewContainerRefを使用する代わりに注入できViewChild、それ<dcl-wrapper>自体がターゲットになります。要素はターゲットの兄弟として追加されるため、<dcl-wrapper>この方法の外側になります。
ギュンターZöchbauer

1
交換はサポートされていません。あなたがテンプレートに変更することができます''(空の文字列) `とにコンストラクタを変更constructor(private target:ViewContainerRef) {}し、動的に追加のコンポーネントはの兄弟になる<dcl-wrapper>
ギュンターZöchbauer

1
私はRC4を使用しており、例は非常に役に立ちました。私が言及したいことは、this.cmpRef.changeDetectorRef.detectChanges();を正しく機能させるために、バインディングのコードを以下に追加する必要があることだけです。
Rajee

4
ngAfterViewInitを使用しているときに、動的コンポーネントに別のdynaimcコンポーネントがあると、エラーが発生しました。代わりに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.