Angular2 NG-шаблон в отдельном файле
angular2 как использовать ng-шаблон из другого файла? Когда я помещаю ng-шаблон в тот же HTML, где я использую, он работает, но когда я перемещаю ng-шаблон в отдельный файл, он не работает. Есть ли способ переместить ng-шаблон в свой собственный файл и использовать его в другом файле HTML?
инфо-message.html
<ng-template #messageTemplate>
Hi
</ng-template>
<ng-container *ngTemplateOutlet="messageTemplate;"></ng-container>
выше работает нормально, потому что NG-шаблон и использование находится в одном файле
сообщение-template.html
<ng-template #messageTemplate>
Hi
</ng-template>
инфо-message.html
<ng-container *ngTemplateOutlet="messageTemplate;"></ng-container>
Это не работает. Есть ли способ использовать "messageTemplate", который находится в отдельном файле внутри другого HTML (например, info-message.html)
Заранее спасибо.
1 ответ
Такое поведение может быть достигнуто через "портал". Это полезный и довольно распространенный шаблон в приложениях Angular. Например, у вас может быть глобальная боковая панель, расположенная рядом с верхним уровнем приложения, и тогда дочерние компоненты могут указывать локальный <ng-template/>
как часть их общего шаблона, который будет отображаться в этом месте.
Обратите внимание, что в то время как <ng-template/>
может быть определен вне файла, где определен желаемый выход, все еще необходимо поместить <ng-template/>
внутри шаблона какого- то компонента. Это может быть минималистский компонент, который отвечает только за <ng-template/>
Однако это может быть и сложный компонент, где <ng-template/>
представляет интерес только незначительную роль.
Этот код иллюстрирует одну возможную базовую реализацию портала.
@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;
}
}
Он работает, используя три части:
- Директива "портала". Это живет по желанию
<ng-template/>
и принимает в качестве входных данных название торговой точки, в которой должен отображаться контент. - Директива "Портал". Это живет на выходе, например
<ng-container/>
и определяет выход. - Сервис портала. Это предоставляется на корневом уровне и хранит ссылки на порталы портала, чтобы порталы могли получить к ним доступ.
Это может показаться большой работой для чего-то довольно простого, но как только эта сантехника будет на месте, ее будет легко (повторно) использовать.
<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>
В общем, изобретать велосипед не очень хорошая идея, хотя, когда уже есть хорошо протестированные, документированные и стабильные реализации. В Angular CDK есть такая реализация, и я бы посоветовал использовать ее на практике, а не свою.
Вы видели это? https://github.com/angular/angular/issues/27503 Там есть пример, предоставленный dawidgarus
Предполагается, что если вы хотите повторно использовать свой шаблон в разных файлах, вам следует преобразовать то, что находится внутри шаблона, в отдельный компонент, а затем вы можете использовать этот компонент где угодно.
Если вы загружаете отдельный файл, вы можете определить компонент в отдельном файле (вместо <ng-template>
). А затем введите весь компонент в<ng-container>
используя *ngComponentOutlet
.
Вы можете найти полную версию с примером здесь: /questions/53058488/vnedrenie-komponenta-v-ng-kontejner-zagruzit-ng-shablon-iz-fajla/53058496#53058496
Расширение ответа @peter554 по причинам объяснения и переносимости. Это позволит вам использовать шаблон во всех компонентах.
Использовать:
'app.module.ts'
import {NgModule} from '@angular/core';
import {
IdcPortalDirective, IdcTemplatePortalDirective,
PortalService
} from './idc-template-portal/idc-template-portal.component';
@NgModule({
declarations: [
IdcPortalDirective,
IdcTemplatePortalDirective
],
imports: [],
exports: [],
providers: [
PortalService
],
bootstrap: [AppComponent]
})
export class AppModule {}
'./idc-template-portal/idc-template-portal.component.ts'
import {
AfterViewInit,
Directive,
Injectable,
Input,
OnInit, Output,
TemplateRef,
ViewContainerRef
} from '@angular/core';
/*** Input Template ***/
/*** <ng-template idcPortal [outlet]="'outletname'">Template Contents</ng-template> ***/
@Directive({
selector: '[idcPortal]'
})
export class IdcPortalDirective implements OnInit {
@Input() outlet: string;
@Output() inlet: string = this.outlet;
constructor(private portalService: PortalService, public templateRef: TemplateRef<any>) {}
ngOnInit():void {
this.portalService.registerInlet(this);
}
}
/*** Output Container ***/
/*** <ng-container [idcPortalOutlet]="'outletname'"></ng-container> ***/
@Directive({
selector: '[idcPortalOutlet]'
})
export class IdcTemplatePortalDirective implements OnInit, AfterViewInit {
@Input() appPortalOutlet: string;
@Output() outlet: string = this.appPortalOutlet;
constructor(private portalService: PortalService, public viewContainerRef: ViewContainerRef) {}
ngOnInit():void {
this.portalService.registerOutlet(this);
}
ngAfterViewInit() {
this.portalService.initializePortal(this.appPortalOutlet);
}
}
@Injectable({
providedIn: 'root'
})
export class PortalService {
outlets = new Map<string, IdcTemplatePortalDirective>();
inlets = new Map<string, IdcPortalDirective>();
registerOutlet(outlet: IdcTemplatePortalDirective) {
this.outlets[outlet.outlet] = outlet;
}
registerInlet(inlet: IdcPortalDirective) {
this.inlets[inlet.inlet] = inlet;
}
initializePortal(portal:string) {
const inlet: IdcPortalDirective = this.inlets[portal];
const outlet: IdcTemplatePortalDirective = this.outlets[portal];
outlet.viewContainerRef.clear();
outlet.viewContainerRef.createEmbeddedView(inlet.templateRef);
}
}
Он,@peter554, упоминает об изобретении велосипеда в отношении пакета порталов Angular CDK. Однако я считаю, что его / эта реализация имеет больше смысла в том, как она используется в потоке приложения, и в простоте, с которой шаблон может быть перенесен из компонента в другой компонент, который содержит выход портала (позволяя компоненту компонент-> шаблон портала Например, в шаблоне компонента, реализующем Angular Material MatBottomSheet (idcBottomSheet)).
Элемент input:
<!--
/*
For example, perhaps you have a mobile view
where a template is hidden (via css) and ported
over to a MatBottomSheet component template to be
popped up when requested (by button click).
*/
-->
<button #bottomsheetButton (click)="openBottomSheet(Notes)" mat-button>
<mat-icon>notes</mat-icon>
</button>
<!--/* hidden in mobile view mode. */-->
<ng-content *ngTemplateOutlet="Notes"></ng-content>
<ng-template #Notes idcPortal [outlet]="'idcBottomSheet'"><!--/* template to port */-->
<form>
<mat-form-field class="w-100 h-100">
<mat-label>A place for your thoughts:</mat-label>
<textarea matInput
cdkTextareaAutosize
#autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="10"
cdkAutosizeMaxRows="10"
placeholder="Angular. It makes me feel...">
</textarea>
</mat-form-field>
</form>
</ng-template>
Элемент output (внутри шаблона компонента MatBottomSheet):
<ng-container [idcPortalOutlet]="'appIdcBottomSheet'"></ng-container>
Вы можете использовать что-то вроде этого (шаблон используется из другого компонента):
@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;
}
}