Передача компонента в качестве "аргумента" другому компоненту в Angular 2

Я новичок в веб-разработке, и я только начал создавать приложение Angular 2. На данный момент я пытаюсь создать некоторые компоненты/формы CRUD, но я нахожу, что сам дублирую много кода. Я не буду спрашивать, какие общие рекомендации избегать дублирования кода и обеспечить повторное использование компонентов при разработке приложений CRUD с помощью Angular2, потому что сообщение будет заблокировано. Я предпочту сосредоточиться на одном конкретном аспекте:

У меня есть страница "CRUD", в которой есть список (это фактически таблица html) ресурсов и несколько кнопок, которые открывают формы "создавать", "читать" и "редактировать". Этот список является отдельным компонентом самостоятельно и отдельными компонентами create/read/edit. Каждая строка таблицы содержит другой компонент, который знает, как визуализировать элемент ресурса. Я назову этот компонент <resource-item>. Тем не менее, у меня есть несколько таких "страниц CRUD", каждая страница для другого ресурса. Поэтому я хочу повторно использовать компонент списка для всех ресурсов. Поэтому первое, что нужно сделать, - добавить в компонент списка входы или атрибуты, чтобы управлять своими метками. Все идет нормально.

Но как насчет компонента <resource-item>? Каждый ресурс моего приложения может иметь совершенно другую структуру. В результате мне понадобятся разные компоненты для разных ресурсов, например: <resource-a-item>, <resource-b-item> и т.д. Как я могу указать, какой компонент элемента ресурса я хочу использовать каждый раз при создании компонента списка?

Спасибо за ваше время.

Ответ 1

Это, по-видимому, идеально подходит для пересылки контента.

Angular 2 поставляется с компонентом ng-content, который позволяет вставлять внешние html/компоненты в качестве содержимого вашего компонента.

Вам просто нужно использовать в том месте, где вы хотите, чтобы содержимое отображалось в вашем компоненте.

Например:

import {Component} from 'angular2/core'

@Component({
  selector: 'holder',
  providers: [],
  template: `
    <div>
      <h2> Here the content I got </h2>
      <ng-content></ng-content>
    </div>
  `,
  directives: []
})
export class Holder {
  constructor() {

  }
}

И вы можете указать содержимое, которое вы хотите ввести из родительского элемента таким образом:

import {Component} from 'angular2/core';
import {Holder} from './holder';

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <holder>
        <div>yeey transcluded content {{name}}</div>
      </holder>
    </div>
  `,
  directives: [Holder]
})
export class App {
  constructor() {
    this.name = 'Angular2'
  }
}

Вы можете увидеть рабочий пример здесь.

В вашем случае вы можете сделать строку/элемент списка компонентом, который может принять некоторый контент для отображения.

Ответ 2

Я работаю над сценарием многократного использования, который может вам пригодиться. В этом примере это компонент configurator, который имеет базовую структуру и используется для настройки других компонентов через объект values (исходит от form.values).

import {Component, Input, EventEmitter, ViewEncapsulation} from 'angular2/core';

@Component({
  encapsulation: ViewEncapsulation.None,
  selector: 'configurator',
  template: `
    <div [hidden]="!active">
      <span (click)="active = false">&times;</span>
      <h3>{{ title }}</h3>
      <form>
        <ng-content></ng-content>
      </form>
    </div>
  `
})
export class ConfiguratorComponent {
  @Input() title: string;
  @Input() values: any = {};
  @Input() emitter: EventEmitter<any>;

  public active: boolean = false;

  set(key: string, value?: any) {
    this.values[key] = value || !this.values[key];
    if (this.emitter)
      this.emitter.emit(this.values);
  }
}

Я использую его в хост-компоненте как дочерний компонент компонента, который он конфигурирует.

@Component({
  directives: [
    ConfiguratorComponent,
    ResourceOneComponent,
  ],
  pipes: [...],
  providers: [...],
  template: `
  <configurator title="Resource One" #cfg [values]="one.values" [emitter]="configEmitter">

    <label>Size:
      <input type="number" min="0" #size [value]="cfg.values.size" (change)="cfg.set('size', size.value)">
    </label>

  </configurator>
  <resource-one #one [emitter]="configEmitter"></resource-one>
  `
})
class HostComponent {
  public configEmitter = EmitterService.get('resource_one');
}

Компонент ресурса может быть:

class ResourceOneComponent extends CRUDComponent {
  public values: { size: 5 };
  private ngOnChanges() {
    if (this.emitter) 
      this.emitter.subscribe(values => {
        // use values from configurator
      });
  }
}

Это сервис, который я использую для связи между компонентами-братьями:

import {Injectable, EventEmitter} from 'angular2/core';

@Injectable()
export class EmitterService {
  private static _emitters: { [ID: string]: EventEmitter<any> } = {};
  static get(channel: string): EventEmitter<any> {
    if (!this._emitters[channel]) 
      this._emitters[channel] = new EventEmitter();
    return this._emitters[channel];
  }
}

РЕДАКТИРОВАТЬ: Это может быть "излишний" для вашего случая использования (: я только что видел другие ответы, которые действительны для более простых сценариев... Мне нравится держать все как можно больше, поэтому каждый компонент делает одно: я искал способы общения между компонентами многократного использования, и это решение - это то, что работает хорошо. Думал, что было бы полезно поделиться (;

Ответ 3

Вы можете использовать ngSwitch (ng-switch в Angular2), если список разных resource-x-item> исправлен. Вы также можете использовать маршрутизацию для динамического добавления компонентов. Если вышеуказанное не работает для вашего случая использования, вы можете использовать DynamicComponentLoader` (Как использовать angular2 DynamicComponentLoader в ES6?)