18

angular2 如何使用来自不同文件的 ng-template?当我将 ng-template 放在我使用它的同一个 HTML 中时,但是当我将 ng-template 移动到单独的文件中时,它就不起作用了。有没有办法将 ng-template 移动到自己的文件中并在不同的 html 文件中使用它?

信息消息.html

<ng-template #messageTemplate>
    Hi
</ng-template>

<ng-container *ngTemplateOutlet="messageTemplate;"></ng-container>

以上工作正常,因为 ng-template 和用法在同一个文件中

消息模板.html

<ng-template #messageTemplate>
    Hi
</ng-template>

信息消息.html

<ng-container *ngTemplateOutlet="messageTemplate;"></ng-container>

这是行不通的。有没有办法使用“messageTemplate”,它位于另一个 html 内的单独文件中(例如:info-message.html)

提前致谢。

4

5 回答 5

6

这种行为可以通过“门户”来实现。这是 Angular 应用程序中一种有用且相当常见的模式。例如,您可能有一个位于顶级应用程序级别附近的全局侧边栏出口,然后子组件可以指定一个 local<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/>中,并将应呈现内容的出口的名称作为输入。
  • “门户网站”指令。这存在于一个出口上,例如 an <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提供了这样一个实现,我建议在实践中使用那个而不是你自己的。

于 2019-04-25T16:20:09.820 回答
4

你见过这个吗?https://github.com/angular/angular/issues/27503 dawidgarus 提供了一个示例

建议是,如果您想在不同的文件中重用模板,您应该将模板内的内容转换为单独的组件,然后您可以在任何地方重用该组件。

于 2019-04-25T15:21:18.563 回答
0

如果您正在加载单独的文件,您可以在单独的文件中定义一个 Component (而不是 a <ng-template>)。然后将整个组件注入到<ng-container>使用*ngComponentOutlet.

您可以在此处找到完整的示例:https ://stackoverflow.com/a/59180628/2658683

于 2019-12-05T09:08:19.870 回答
0

出于解释和可移植性的原因,扩展了@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) 的组件模板中。

“输入”元素:

<!--
/*
  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>

“输出”元素(在您的 MatBottomSheet 组件模板内):

<ng-container [idcPortalOutlet]="'appIdcBottomSheet'"></ng-container>
于 2020-03-06T04:37:58.037 回答
0

你可以使用这样的东西(模板是从另一个组件中使用的):

@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;
    }
}
于 2018-03-21T11:23:21.583 回答