如何在Angular中使用NgTemplateOutlet创建可重用组件

来自菜鸟教程
跳转至:导航、​搜索

介绍

单一责任原则 是您的应用程序的各个部分应该有一个目的的想法。 遵循这个原则可以让你的 Angular 应用更容易测试和开发。

在 Angular 中,使用 NgTemplateOutlet 而不是创建特定组件,可以轻松针对各种用例修改组件,而无需修改组件本身!

在本文中,您将使用现有组件并重写它以使用 NgTemplateOutlet

先决条件

要完成本教程,您需要:

  • Node.js 安装在本地,您可以按照【X57X】如何安装Node.js 并创建本地开发环境【X126X】进行。
  • 熟悉 设置 Angular 项目

本教程已使用 Node v16.6.2、npm v7.20.6 和 @angular/core v12.2.0 进行了验证。

第 1 步 – 构建 CardOrListViewComponent

考虑 CardOrListViewComponent,它根据 mode'card''list' 格式显示 items

它由一个 card-or-list-view.component.ts 文件组成:

卡片或列表视图.component.ts

import {
  Component,
  Input
} from '@angular/core';

@Component({
  selector: 'card-or-list-view',
  templateUrl: './card-or-list-view.component.html'
})
export class CardOrListViewComponent {

  @Input() items: {
    header: string,
    content: string
  }[] = [];

  @Input() mode: string = 'card';

}

还有一个 card-or-list-view.component.html 模板:

card-or-list-view.component.html

<ng-container [ngSwitch]="mode">
  <ng-container *ngSwitchCase="'card'">
    <div *ngFor="let item of items">
      <h1>{{item.header}}</h1>
      <p>{{item.content}}</p>
    </div>
  </ng-container>
  <ul *ngSwitchCase="'list'">
    <li *ngFor="let item of items">
      {{item.header}}: {{item.content}}
    </li>
  </ul>
</ng-container>

这是该组件的使用示例:

使用.component.ts

import { Component } from '@angular/core';

@Component({
  template: `
    <card-or-list-view
        [items]="items"
        [mode]="mode">
    </card-or-list-view>
`
})
export class UsageExample {
  mode = 'list';
  items = [
    {
      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
      content: 'The single responsibility principle...'
    } // ... more items
  ];
}

这个组件没有单一的职责,也不是很灵活。 它需要跟踪它的 mode 并知道如何在 cardlist 视图中显示 items。 并且只能显示 itemsheadercontent

让我们通过使用模板将组件分解为单独的视图来改变这一点。

第 2 步 – 了解 ng-templateNgTemplateOutlet

为了让 CardOrListViewComponent 显示任何类型的 items 我们需要能够告诉它如何显示它们。 我们可以通过给它一个模板来实现这一点,它可以用来删除 items

模板将是使用 <ng-template>TemplateRefs,而图章将是从 TemplateRefs 创建的 EmbeddedViewRefsEmbeddedViewRefs 在 Angular 中用自己的上下文表示视图,是最小的基本构建块。

Angular 提供了一种使用 NgTemplateOutlet 从模板中删除视图的概念的方法。

NgTemplateOutlet 是一个指令,它接受 TemplateRef 和上下文,并使用提供的上下文标记出 EmbeddedViewRef。 通过 let-模板:TemplateVariableName="contextProperty" 属性在模板上访问上下文以创建模板可以使用的变量。 如果未提供上下文属性名称,它将选择 $implicit 属性。

这是一个例子:

import { Component } from '@angular/core';

@Component({
  template: `
    <ng-container *ngTemplateOutlet="templateRef; context: exampleContext"></ng-container>
    <ng-template #templateRef let-default let-other="aContextProperty">
      <div>
        $implicit = '{{default}}'
        aContextProperty = '{{other}}'
      </div>
    </ng-template>
`
})
export class NgTemplateOutletExample {
  exampleContext = {
    $implicit: 'default context property when none specified',
    aContextProperty: 'a context property'
  };
}

这是示例的输出:

<div>
  $implicit = 'default context property when none specified'
  aContextProperty = 'a context property'
</div>

defaultother 变量由 let-defaultlet-other="aContextProperty" 属性提供。

第 3 步 – 重构 CardOrListViewComponent

为了给 CardOrListViewComponent 提供灵活性并允许它显示任何类型的 items,我们将创建两个结构指令作为模板读入。 这些模板将是卡片和列表项。

这里是 card-item.directive.ts

卡片项目.directive.ts

import { Directive } from '@angular/core';

@Directive({
  selector: '[cardItem]'
})
export class CardItemDirective {

  constructor() { }

}

这里是 list-item.directive.ts

列表项.directive.ts

import { Directive } from '@angular/core';

@Directive({
  selector: '[listItem]'
})
export class ListItemDirective {

  constructor() { }

}

CardOrListViewComponent 将导入 CardItemDirectiveListItemDirective

卡片或列表视图.component.ts

import {
  Component,
  ContentChild,
  Input,
  TemplateRef 
} from '@angular/core';
import { CardItemDirective } from './card-item.directive';
import { ListItemDirective } from './list-item.directive';

@Component({
  selector: 'card-or-list-view',
  templateUrl: './card-or-list-view.component.html'
})
export class CardOrListViewComponent {

  @Input() items: {
    header: string,
    content: string
  }[] = [];

  @Input() mode: string = 'card';

  @ContentChild(CardItemDirective, {read: TemplateRef}) cardItemTemplate: any;
  @ContentChild(ListItemDirective, {read: TemplateRef}) listItemTemplate: any;

}

该代码将在我们的结构指令中读取为 TemplateRefs

card-or-list-view.component.html

<ng-container [ngSwitch]="mode">
  <ng-container *ngSwitchCase="'card'">
    <ng-container *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="cardItemTemplate"></ng-container>
    </ng-container>
  </ng-container>
  <ul *ngSwitchCase="'list'">
    <li *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="listItemTemplate"></ng-container>
    </li>
  </ul>
</ng-container>

这是该组件的使用示例:

使用.component.ts

import { Component } from '@angular/core';

@Component({
  template: `
    <card-or-list-view
        [items]="items"
        [mode]="mode">
      <div *cardItem>
        Static Card Template
      </div>
      <li *listItem>
        Static List Template
      </li>
    </card-or-list-view>
`
})
export class UsageExample {
  mode = 'list';
  items = [
    {
      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
      content: 'The single responsibility principle...'
    } // ... more items
  ];
}

通过这些更改,CardOrListViewComponent 现在可以根据提供的模板在卡片或列表表单中显示任何类型的项目。 目前,模板是静态的。

我们需要做的最后一件事是通过给模板一个上下文来允许模板是动态的:

card-or-list-view.component.html

<ng-container [ngSwitch]="mode">
  <ng-container *ngSwitchCase="'card'">
    <ng-container *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="cardItemTemplate; context: {$implicit: item}"></ng-container>
    </ng-container>
  </ng-container>
  <ul *ngSwitchCase="'list'">
    <li *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="listItemTemplate; context: {$implicit: item}"></ng-container>
    </li>
  </ul>
</ng-container>

这是该组件的使用示例:

使用.component.ts

import { Component } from '@angular/core';

@Component({
  template: `
    <card-or-list-view
        [items]="items"
        [mode]="mode">
      <div *cardItem="let item">
        <h1>{{item.header}}</h1>
        <p>{{item.content}}</p>
      </div>
      <li *listItem="let item">
        {{item.header}}: {{item.content}}
      </li>
    </card-or-list-view>
`
})
export class UsageExample {
  mode = 'list';
  items = [
    {
      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
      content: 'The single responsibility principle...'
    } // ... more items
  ];
}

有趣的是,我们使用星号前缀和 microsyntax 作为语法糖。 它与以下内容相同:

<ng-template cardItem let-item>
  <div>
    <h1>{{item.header}}</h1>
    <p>{{item.content}}</p>
  </div>
</ng-template>

就是这样! 我们有原始的功能,但是现在我们可以通过修改模板来显示我们想要的任何内容,并且 CardOrListViewComponent 的责任更少。 我们可以向项目上下文添加更多内容,例如 firstlast,类似于 ngFor 或显示完全不同类型的 items

结论

在本文中,您使用了一个现有组件并将其重写为使用 NgTemplateOutlet

如果您想了解有关 Angular 的更多信息,请查看 我们的 Angular 主题页面 以获取练习和编程项目。