angular2 ng-template in a separate file

主宰稳场 提交于 2019-12-05 00:46:59

Have you seen this? https://github.com/angular/angular/issues/27503 There is an example there provided by dawidgarus

The suggestion is that if you want to reuse your template in different files, you should convert what is inside the template into a separate component, then you can reuse that component wherever you want.

You can use something like this (template is used from another component):

@Component(
    template: '<ng-container *ngTemplateOutlet="infoMessage.template;"></ng-container>'
)
export class MessageTemplate {
    infoMessage: InfoMessage;    
}

@Component(
    ....
)
export class InfoMessage{    
    @ContentChild('columnTemplate') template: TemplateRef<any>;

    constructor(private messageTemplate: MessageTemplate) {
        messageTemplate.infoMessage = this;
    }
}

This behaviour can be achieved via a 'portal'. This is a useful and fairly common pattern in Angular applications. For example you may have a global sidebar outlet living near the top app level and then child components may specify a local <ng-template/>, as part of their overall template, to be rendered at this location.

Note that while the <ng-template/> may be defined outside of the file where the desired outlet is defined, it is still necessary to place the <ng-template/> inside the template of some component. This can be a minimalist component which is only responsible for wrapping the <ng-template/>, however it could equally be a complicated component where the <ng-template/> of interest only plays a minor part.

This code illustrates one possible basic implementation of a portal.

@Directive({
  selector: '[appPortal]'
})
export class PortalDirective implements AfterViewInit {
  @Input() outlet: string;

  constructor(private portalService: PortalService, private templateRef: TemplateRef<any>) {}

  ngAfterViewInit(): void {
    const outlet: PortalOutletDirective = this.portalService.outlets[this.outlet];
    outlet.viewContainerRef.clear();
    outlet.viewContainerRef.createEmbeddedView(this.templateRef);
  }
}

@Directive({
  selector: '[appPortalOutlet]'
})
export class PortalOutletDirective implements OnInit {
  @Input() appPortalOutlet: string;

  constructor(private portalService: PortalService, public viewContainerRef: ViewContainerRef) {}

  ngOnInit(): void {
    this.portalService.registerOutlet(this);
  }
}

@Injectable({
  providedIn: 'root'
})
export class PortalService {
  outlets = new Map<string, PortalOutletDirective>();

  registerOutlet(outlet: PortalOutletDirective) {
    this.outlets[outlet.appPortalOutlet] = outlet;
  }
}

It works using three parts:

  • A 'portal' directive. This lives on the desired <ng-template/> and takes as input the name of the outlet at which the content should be rendered.
  • A 'portal outlet' directive. This lives on an outlet, e.g. an <ng-container/>, and defines the outlet.
  • A 'portal' service. This is provided at the root level and stores references to the portal outlets so they can be accessed by the portals.

This may seem like a lot of work for something quite simple but once this plumbing is in place it is easy to (re)use.

<div class="container">
  <div class="row">
    <div class="col-6">
      <app-foo></app-foo>
    </div>
    <div class="col-6">
      <ng-container [appPortalOutlet]="'RightPanel'"></ng-container>
    </div>
  </div>
</div>

// foo.component.html
<h1>Foo</h1>
<ng-template appPortal [outlet]="'RightPanel'">
 <h1>RIGHT</h1>
</ng-template>

In general it's not a great idea to reinvent the wheel though when there are already well-tested, documented and stable implementations available. The Angular CDK provides such an implementation and I'd advise to use that one rather than your own in practice.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!