*ngTemplateOutlet 指令的实际场景是什么?

What are practical scenarios of *ngTemplateOutlet directive?

我正在阅读有关 *ngTemplateOutlet 指令的内容。该指令的用途是通过模板引用和上下文对象作为参数动态实例化模板。

我想知道的是,我们在Angular中有很多东西可以达到与*ngTemplateOutlet相同的结果,例如:

  1. 我们可以有多个 *ngIf,它们可以根据同一组件中的组件变量值呈现不同的模板。以类似的方式,我们有 [ngSwitch] 会根据不同的值为我们呈现不同的模板。

  2. 我们可以通过引用相应变量的模板引用变量来使用 *ngIf 的引用。

对于前一种情况:

<div *ngIf="condition1"> Content 1 </div>
<div *ngIf="condition2"> Content 2 </div>
<div *ngIf="condition3"> Content 3 </div>

对于后者:

<ng-container *ngIf="condition then myTemplate else otherTemplate"></ng-container>
<ng-template #myTemplate> Some content... </ng-template>
<ng-template #otherTemplate> Some content... </ng-template>

如果我们的武器库中有这样的方法,*ngTemplateOutlet 还能增加什么价值?

在哪些实际用例(如果有的话)中我们不能使用上述方法而应该使用 *ngTemplateOutlet 指令,或者它只是另一种方法可供选择以达到相同的结果?

你的问题很有道理。如果可以通过简单的 ifswitch 案例来实现某些事情,我们为什么要使用 *ngTemplateOutlet

独立组件

你有这些想法是因为你在考虑一个独立的组件级别。换句话说,所有条件、模板都在同一个组件中。我们可以很容易地select根据一定的条件制作模板。

库组件

Dynamic Template

当我说库组件时,它是指通用可重用组件,例如 AutocompleterTypeahead 等。这些组件提供功能部分,但它们允许开发人员选择自己的 template根据他们的需要。

问题来了,这些模板不在 Autocompleter 中,而是来自 @ContentChild

例如:

<ng-autocompleter>
   <ng-template #item let-item>{{item.name}}</ng-template>
<ng-autocompleter>

在上面的示例中,<ng-template> 是开发人员稍后定义的,它不是 <ng-autocompleter> 的直接部分。

Template Context

每当开发高度配置的组件时,模板上下文都非常重要。获取动态模板 (html) 不足以达到目的。我们需要将值绑定到 ng-template。由于 ng-template 不在 ng-autocompleter 的一部分,我们需要传递包含所有必要数据的上下文来绑定。

ex :在上面的例子中,如果你看到我们用 let-item 声明了 item 变量,但是 item 是从哪里来的。这将由 *ngTemplateOutlet.

的上下文决定

One line conclusion If we want to inject the templates which is will be declared in future by someone, I cannot handle this case by *ngIf or *ngSwitch. We need to use *ngTemplateOutlet.

Angular template outlets 可用于在视图的各个部分中插入通用模板,这些部分不是由循环生成的或不受条件约束的。例如,你可以为一个公司的标志定义一个模板,并将其插入到页面的几个地方:

<div>
  <ng-container *ngTemplateOutlet="companyLogoTemplate"></ng-container>
  <h1>Company History</h1>
  <div>{{companyHistory}}</div>
</div>
<form (ngSubmit)="onSubmit()">
  <ng-container *ngTemplateOutlet="companyLogoTemplate"></ng-container>
  <h1>User info</h1>
  <label>Name:</label><input type="text" [(ngModel)]="userName" />
  <label>Account ID:</label><input type="text" [(ngModel)]="accountId" />
  <button>Submit</button>
</form>
<div class="footer">
  <ng-container *ngTemplateOutlet="companyLogoTemplate"></ng-container>
</div>

<ng-template #companyLogoTemplate>
  <div class="companyLogo">
    <img [src]="logoSourceUrl">
    <label>The ACME company, {{employeeCount}} people working for you!</label>
  </div>
</ng-template>

模板和模板插座也有助于使组件可配置。下面的例子取自this article by Angular University.

选项卡容器组件定义默认选项卡 header 模板,但允许使用定义为输入 属性 的自定义模板覆盖它。然后使用模板出口将适当的模板(默认或自定义)插入视图中:

@Component({
  selector: 'tab-container',
  template: `
    <ng-template #defaultTabButtons>
      <div class="default-tab-buttons">
        ...
      </div>
    </ng-template>
    <ng-container *ngTemplateOutlet="headerTemplate || defaultTabButtons"></ng-container>
    ... rest of tab container component ...
  `
})
export class TabContainerComponent {
    @Input() headerTemplate: TemplateRef<any>; // Custom template provided by parent
}

在 parent 组件中,定义自定义选项卡 header 模板并将其传递给选项卡容器组件:

@Component({
  selector: 'app-root',
  template: `      
    <ng-template #customTabButtons>
      <div class="custom-class">
        <button class="tab-button" (click)="login()">
          {{loginText}}
        </button>
        <button class="tab-button" (click)="signUp()">
          {{signUpText}}
        </button>
      </div>
    </ng-template>
    <tab-container [headerTemplate]="customTabButtons"></tab-container>      
  `
})
export class AppComponent implements OnInit {
  ...
}

您可以在 this blog post by alligator.io 中查看另一个高级用例。

使 *ngTemplateOutlet 比 *ng-content 更强大的一件事是当您将它与 [ngTemplateOutletContext] 输入一起使用时。这使您能够创建一个完全独特的模板,该模板可以使用组件内的状态。

我已经使用它创建了一个 select 组件,该组件通过不同的模板为每个客户端设计了独特的样式,但共享完全相同的代码。这里有一个StackBlitz link and also my article about it on indepth.dev.

*ngTemplateOutlets 优于使用 *ngIfs 的一个例子是您的组件不需要依赖外部包,即图标库,如果它仅供单个客户端使用的话。

当您需要处理不知道可以嵌套到多少层的嵌套项时,可以看到 ngTemplateOutlet 的真正威力。在处理嵌套数组时,执行 ngFor 或 ngSwitch 很麻烦,而且受限,因为你总是需要事先知道你想要进入多少层:

例如:

如果这是我的数组:

  todo = [
    'Get to work',
    [
      'Get up',
      'Brush teeth',
      'Take a shower',
      'Check e-mail',
      'Walk dog'
    ],
    [
      'Prepare for work',
      'Drive to office',
      'Park car',
      'Work',
      [
        'See Clients',
        'Fill Out Forms',
        'Take Tea Breaks',
        [
          'Switch on Kettle',
          'Get Clean Cup',
          'Add Tea',
          'Pour Water',
          'Drink'
        ]
      ]
    ],
    'Pick up groceries',
    'Go home',
    'Fall asleep'
  ];

那么你可以使用以下方法:

<div *ngFor="let item of todo" style="margin-left: 25px">

  <div *ngIf="!isArray(item); else arrayView">{{item}}</div>
  <ng-template #arrayView>

    <div *ngFor="let item2 of item" style="margin-left: 25px">

      <p>And So On...</p>

    </div>

  </ng-template>

</div>

但是,如果您不知道数组的深度有多少层怎么办?您可以将 ngTemplateOutlet 与 ng-template 结合使用来解决问题:

<ng-template #tmplItems let-todo="todo">

  <div *ngFor="let item of todo" style="margin-left: 25px">

    <div *ngIf="!isArray(item); else arrayView">{{item}}</div>
    <ng-template #arrayView>

      <ng-container *ngTemplateOutlet="tmplItems,context:{todo:item}">

      </ng-container>

    </ng-template>

  </div>

</ng-template>

<div *ngFor="let item of todo">

  <div *ngIf="!isArray(item); else arrayView">{{item}}</div>
  <ng-template #arrayView>

    <ng-container *ngTemplateOutlet="tmplItems,context:{todo:item}">

    </ng-container>

  </ng-template>

</div>

现在不管你的数组有多深,递归调用你的 ng-template 来解决这个问题(除非有一个我还不知道的限制)。

输出:

Get to work
    Get up
    Brush teeth
    Take a shower
    Check e-mail
    Walk dog
    Prepare for work
    Drive to office
    Park car
    Work
        See Clients
        Fill Out Forms
        Take Tea Breaks
            Switch on Kettle
            Get Clean Cup
            Add Tea
            Pour Water
            Drink
Pick up groceries
Go home
Fall asleep