How to create Angular 6 component with multiple ng-templates?

Issue

I’ve been reading a lot regarding Angular’s templates but I didn’t find anything that would closely resemble what I’m trying to archieve, not to mention that templating in Angular 2+ is confusing as hell which doesn’t make the task easier than it should be.

I’d like to know how can I do something like this (or if it’s even possible):

<my-form>
  <ng-template [for]="title">Users</ng-template>
  <ng-template [for]="content">
    <form>
      <input type="text" name="username">
      <input type="text" name="password">
    </form>
  </ng-template>
  <ng-template [for]="footer">
    <button (click)="edit()">Edit</button>
    <button (click)="delete()">Delete</button>
  </ng-template>
</my-form>

So, the idea would be that my my-form component would have stuff like markup, styles, and common stuff that would apply to all these “common forms” I want to create in my application. I’ve been googling all day without avail, only to find this article which kinda resembles what I’m trying to do, but it’s not close and the article is also hard to understand for me.

Note: I don’t even know if the [for] tag is accurate, this is off the top of my head. Custom tags would work (eg. <my-form-title></my-form-title>, <my-form-content></my-form-content>, <my-form-footer></my-form-footer> and so on).

Can someone here please help me? Thanks!

Solution

Every Angular structural directive turns the element it’s attached to with a ng-template, so you can use custom directives to “mark” children as templates.

First you create a bunch of directives for the customizable parts of your component:

@Directive({
  selector: '[myFormTitle]'
})
export class TitleDirective {}

@Directive({
  selector: '[myFormContent]'
})
export class ContentDirective {}

@Directive({
  selector: '[myFormFooter]'
})
export class FooterDirective {}

You use @ContentChild to query the component tree to grab the templates created by these directives:

@Component({
  selector: 'my-form',
  templateUrl: './my-form.component.html',
})
export class MyFormComponent implements OnInit {
  @ContentChild(TitleDirective, {read: TemplateRef})
  titleTemplate: TemplateRef<any>;

  @ContentChild(ContentDirective, {read: TemplateRef})
  contentTemplate: TemplateRef<any>;

  @ContentChild(FooterDirective, {read: TemplateRef})
  footerTemplate: TemplateRef<any>;

  constructor() { 
  }

  ngOnInit() {
    console.log(this)
  }
}

And then you render these in your component template as usual:

<form>
  <h1>
    <ng-container *ngTemplateOutlet="titleTemplate"></ng-container>
  </h1>
  <hr/>
  <ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
  <hr/>
  <small>
    <ng-container *ngTemplateOutlet="footerTemplate"></ng-container>
  </small>
</form>

You use this component like this:

<my-form>
  <span *myFormTitle>BIG LETTERS</span>
  <div *myFormContent>
    <label>Name: <input name='name'/></label>
  </div>
  <span *myFormFooter>
    yadda blah
  </span>
</my-form>

You can also use the directive microsyntax in the *myFormTitle etc. attributes if you’re passing a context to the templates; or to access the inherited context. I’m not going to go in depth into that here though since it doesn’t really fit your use case.

Answered By – millimoose

Answer Checked By – Cary Denson (AngularFixing Admin)

Leave a Reply

Your email address will not be published.