【问题标题】:What are practical scenarios of *ngTemplateOutlet directive?*ngTemplateOutlet 指令的实际场景是什么?
【发布时间】:2019-03-25 07:38:32
【问题描述】:

我正在阅读有关 *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 指令,还是只是另一种方法可以选择以达到相同的结果?

【问题讨论】:

标签: angular directive ng-template


【解决方案1】:

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

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

@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
}

在父组件中,定义自定义标签页眉模板,并将其传递给标签容器组件:

@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 {
  ...
}

您可以在alligator.iothis blog post 中看到另一个高级用例。

【讨论】:

  • 您如何区分使用 *ngTemplateOutlet 来处理后一个选项卡示例的方法与 Angular 中的内容投影方法?我的意思是我也可以通过 Content Projection 获得相同的结果。
  • 对于单个模板,这可能是正确的。如果组件的多个部分是可配置的(例如页眉、内容、页脚),您将不得不使用模板,因为您只能提供一个内容。
  • 但即使在单个组件中,我也可以提供页眉、内容、页脚,然后使用 ng-content 的“选择”属性来相应地放置它们。那么它们有什么不同呢?
  • 您将如何使用ng-contentselect 实现上面的示例?如果没有提供投影内容的标题部分,您将如何显示默认标题模板?
  • 但是,我必须说条件可以用ngIf而不是ngTemplateOutlet来实现。
【解决方案2】:

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

我已经使用它创建了一个选择组件,该组件通过不同的模板为每个客户端设置了独特的样式,但共享完全相同的代码。这是StackBlitz link,也是我在indepth.dev 上的文章。

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

【讨论】:

  • 我是你的忠实粉丝。你关于模板出口的谈话真的很酷。也许你可以回答下面的问题stackoverflow.com/questions/64226242/…
  • 很高兴你喜欢这个演讲! :) 我已经发布了您问题的答案。
  • 由于您的 stackblitz 链接,我也是大粉丝。我给你的答案两票:)
  • ngTemplateOutletContext 在 Angular 11 中不再存在,如何在子组件中注入变量?
  • ngTemplateOutletContext 仍然是 Angular 11。上面的 StackBlitz 链接使用的是 Angular 11。
【解决方案3】:

您的问题非常有效。如果可以通过简单的ifswitch 的情况来实现,我们为什么要使用*ngTemplateOutlet

独立组件

您得到这些想法是因为您正在考虑一个独立的组件级别。换句话说,一切条件,模板都在同一个组件中。我们可以很容易地根据一定的条件选择模板。

库组件

动态模板

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

现在要注意了,这些模板不驻留在Autocompleter,它来自它的@ContentChild

例如:

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

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

模板上下文

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

ex :在上述情况下,如果您看到我们通过let-item 声明了item 变量,但item 来自哪里。这将由*ngTemplateOutlet 的上下文决定。

一行结论 如果我们想注入将来由某人声明的模板,我无法通过 *ngIf 或 *ngSwitch 处理这种情况。我们需要使用*ngTemplateOutlet

【讨论】:

    【解决方案4】:

    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
    

    【讨论】:

      猜你喜欢
      • 2019-11-16
      • 2019-08-27
      • 2019-11-23
      • 1970-01-01
      • 2022-01-22
      • 2011-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多