1. ホーム
  2. アンギュラー

[解決済み】ユーザークリックで選択されたコンポーネントを含むダイナミックタブ

2022-04-18 13:54:55

質問

コンポーネントが自分自身で(タイトルを付けて)登録できるようなタブシステムをセットアップしようとしています。最初のタブは受信箱のようなもので、ユーザーが選択できる多くのアクション/リンク項目があり、これらのクリックのそれぞれは、クリック時に新しいコンポーネントをインスタンス化できるようにする必要があります。アクション/リンクはJSONから入ってきます。

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

これが「ベスト」なアプローチなのかどうか、よくわからないのですが?今のところ、私が見た唯一のガイドは、静的なタブのためのもので、これは役に立ちません。

今のところ、main でブートストラップされる tabs サービスだけを、アプリ全体で永続化させています。それは次のようなものです。

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 しかし、その使用方法についての情報はあまり見当たりません。

EDITです。 明確化の試み。

受信箱はメールの受信箱と同じだと考えてください。アイテムはJSONとして取得され、複数のアイテムを表示します。アイテムの1つがクリックされると、そのアイテムのアクション「タイプ」で新しいタブが作成されます。そして、このタイプはコンポーネントとなります。

EDIT 2: 画像 .

解決方法は?

更新

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


こちらもご覧ください ANGUALL.IO DYNAMIC COMPONENT LOADER(ダイナミックコンポーネントローダー

旧バージョン xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx。

これはAngular2 RC.5で再び変更されました。

以下の例を更新しますが、休暇前の最終日です。

この プランカーの例 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();
    }    
  }
}

プランカー例 RC.4

プランカーエクスペリエンスβ.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();
    }    
  }
}

プランカーエクスペリエンスβ.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];
}

プランカーエクスペリエンス 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