Passing a component as an 'argument' to another component in Angular 2
Asked Answered
K

3

18

I am new to web development and I have just started building an Angular 2 app. At this point I am trying to create some CRUD components/forms but I find my self duplicating a lot of code. I am not going to ask what are the common best practices to avoid code duplication and achieve component reusability when designing CRUD applications with Angular2, because the post will get locked. I will rather focus on one particular aspect:

I have a "CRUD page" that has a list (it is an html table actually) of resources and several buttons that open up "create", "read", and "edit" forms. The list is a separate component on its own and the create/read/edit separate components as well. Each row of the table includes another component which knows how to render a resource item. I will call this <resource-item> component. However, I have several of those "CRUD pages", each page for a different resource. So what I want is to reuse the list component for all the resources. So the first thing to do is to add Inputs or Attributes to the list component in order to control its labels. So far so good.

But what about the <resource-item> component? Each resource of my application might have a completely different structure. As a result I will need different components for different resources, e.g.: <resource-a-item>, <resource-b-item>, etc. How can I specify which resource item component I want to use every time I create a list component?

Thank you for your time.

Kaolin answered 3/1, 2016 at 11:17 Comment(0)
C
22

This seems to be a perfect fit for content transclusion.

Angular 2 comes with a component called ng-content that allows you to insert external html/components as the content of your component.

You just need to use in the place where you want the content to be displayed in your component.

For example:

import {Component} from 'angular2/core'

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

  }
}

And you can specify the content you want injected from the component's parent this way:

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

You can see working example here.

In your case you can make the list row/item a component that can accept some content to display.

Cumber answered 3/1, 2016 at 13:1 Comment(4)
Thank you so much. ng-content saves my day.Torietorii
This answer was so useful. Thanks. I have a question. There is a way to show another component but just show a few columns like name and last name.? Thanks in advance. I do not make a question in SOF because I can'tSensate
@Cumber i write a comment asking for your help. Thanks in advanceSensate
the way to do that is to have the component you want to show be configurable, make it have inputs to show/hide what you need. If you have a more specific case let me know I'd be happy to help.Cumber
M
2

I'm working on reusable component scenario that might be of use to you. In this example it's configurator component that has a basic structure and it's used to configure other components via values object (comes from 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);
  }
}

I use it in a host component as a sibling of component it configures.

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

The resource component could be:

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

This is the service I'm using to communicate between sibling components:

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

EDIT: This might be 'overkill' for your use case (: I just saw other answers, both valid for simpler scenarios... I like to keep things separate as much as possible, so each component does one thing. I've been looking into ways to communicate between reusable components and this solution is something that works nicely. Thought it could be useful to share (;

Midriff answered 3/1, 2016 at 13:24 Comment(0)
P
1

You can use ngSwitch (ng-switch in Angular2) if the list of differentresource-x-item> is fixed. You can also use routing to add components dynamically. If the above don't work for your use case you can use, DynamicComponentLoader` (How to use angular2 DynamicComponentLoader in ES6?)

Pejoration answered 3/1, 2016 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.