5

What I want to do

I want to create a reusable Angular 2 button component that may render as an <a /> tag or an <input /> tag depending on an input to the component called type. I want the button component to accept content children which will be rendered as the button label.

To illustrate: an Angular template that invokes my button component like so:<button>Hello</button> should render as <a>Hello</a> in the DOM. However, if a property type="submit" is set (e.g. <button type="submit>Hello</button>) then the output in the DOM should be <input type="submit">Hello</input>.

To further clarify, if I were using React I could create [an overly simplified version of] this component with:

const Button = ({ type, children }) =>
  type === "submit"
    ? <input type="submit">{children}</input>
    : <a>{children}</a>

Where I'm stuck

Creating an Angular component that displays content children using <ng-content /> was relatively straightforward. However, I'm yet unable to render those children inside a dynamically chosen tag - either <a /> or <input /> depending on the value of the type property.

What I've tried

I initially tried to use <ng-content /> inside an ngIf or ngSwitch directive, only to find out that <ng-content /> can appear at most once in any given template (unless it’s qualified with a selector). However, I want to output all content children so selectors are not helpful.

I keep finding references to DynamicComponentLoader, but that appears to be deprecated.

I've seen the ContentChildren decorator which would allow my button component to access the content children being passed to it, but I'm not sure how to then take those children and inject them into my component template.

I came across NgTemplateOutlet, which seems like it might help me switch between two entirely different templates (one w/ the <a /> tag and one with the <input /> tag). However that's marked as “Experimental” and I’m having trouble understanding the usage.

Any help would be greatly appreciated!!

Elliot
  • 1,463
  • 1
  • 14
  • 14
  • take a look at this plunker to get an idea on how to create a container component which does the trick for you: http://plnkr.co/edit/wh4VJG?p=preview – Yaser Jan 05 '17 at 00:57
  • Angular2 doesn't support a component to replace the tag it is applied to, but it can wrap another component and you can use `*ngIf` inside the component to show ` – Günter Zöchbauer Jan 05 '17 at 07:27
  • @GünterZöchbauer makes sense, but then is there any way to pass content children through to those lower-order components? – Elliot Jan 05 '17 at 13:23
  • Sure. You can access content children using `@ContentChildren(...)` See also http://stackoverflow.com/questions/32693061/angular-2-typescript-get-hold-of-an-element-in-the-template/35209681#35209681 – Günter Zöchbauer Jan 05 '17 at 13:25
  • I may be missing something totally obvious (new to Angular), but if I understand that correctly `@ContentChildren` just allows me to query the children passed to the higher-order component (see the "What I've tried" section of my Q). It doesn't allow me to inject (transclude?) those children back into a specific location in the template or pass them to another component. – Elliot Jan 05 '17 at 13:34

1 Answers1

1
@Component({
  selector: 'adapting-button',
  template: `
      <a *ngIf="type !== "submit">{{value}}</a>
      <input *ngIf="type === "submit" type="submit" [value]="value">
  `,
})
export class AdaptingButtonComponent {
  @Input() type: any;
  @Input() value: any;
}


@Component({
  selector: 'app-root',
  templateUrl: `
    <adapting-button [type]="'submit'" [value]="Hello"></adapting-button>
  `,
})
export class AppComponent {
  title = 'app works!';
}
Timathon
  • 1,049
  • 9
  • 11
  • It doesn't look like this solves the issue I'm having with content children. I can still only output the children via `` in one of the containers. – Elliot Jan 05 '17 at 13:25
  • I've given it a face-lift. See if it's making any sense. (I can not make use of , switched to a input instead.) – Timathon Jan 05 '17 at 13:53
  • It looks like that I shouldn't have brought in . – Timathon Jan 05 '17 at 14:09
  • Thanks for clarifying! This is probably my fault for not being clear, but it's essential that the component accept content children (not just a value prop). I'll update the Q. – Elliot Jan 05 '17 at 15:30