Angular-专家级编程-四-

55 阅读50分钟

Angular 专家级编程(四)

原文:zh.annas-archive.org/md5/EE5928A26B54D366BD1C7A331E3448D9

译者:飞龙

协议:CC BY-NC-SA 4.0

第九章:Angular 中的 Material Design

Material Design 是新的、备受炒作的设计风格。它取代了扁平设计成为新的必须使用的设计。Material Design 是由 Google 在 2014 年推出的,它扩展了 Google Now 的卡片图案。以下是 Google Now 卡片的图片:

Google Now 卡片。

Material Design 背后的整个理念是建立在基于网格的系统、动画和过渡的响应性基础上,同时增加设计的深度。Material Design 的首席设计师 Matias Duarte 这样说:

“与真实的纸张不同,我们的数字材料可以智能地扩展和重塑。材料具有物理表面和边缘。接缝和阴影提供了关于您可以触摸的内容的含义。”

Material Design 是一套非常精确和完整的规范,可以在这里找到:material.google.com/

任何对 CSS3 和 HTML5 有扎实知识的人都可以阅读文档并实现每个组件。然而,这将需要大量的时间和精力。幸运的是,我们不必等那么久。事实上,一组才华横溢的开发人员组成并为 Angular 创建了一个 Material Design 组件。在撰写本文时,这仍处于测试阶段,这意味着一些组件尚未实现或未完全实现。然而,我很少发现自己因为某个组件不存在或不起作用而被困住,以至于不得不改变整个设计。

在本章中,我们将学习如何安装 Material Design 的 Angular 组件,然后使用一些最受欢迎的组件。我们还将看一下材料图标。更详细地说,我们将看到:

  • 如何为 Angular 安装 Material Design

  • 响应式布局的处理方式

  • 材料图标

  • 按钮

  • 菜单

  • 工具栏

  • 对话框

  • 创建自己的主题

安装包

首先,我们需要安装 Angular Material Design 包。使用 Angular CLI 相对简单:

ng new chap10
cd chap10
npm install --save @angular/material 
npm install --save @angular/animations
npm install --save hammerjs 

我们在这里安装了两个包,@angular/materialhammerjs包。第一个包包括了我们的应用程序中将在下一节中使用的 Material Design 模块。然而,第二个包是触摸移动的 JavaScript 实现。一些 Material Design 组件,如slider,依赖于hammerjs

然后,根据NgModule规范,我们可以导入MaterialModule如下:

//src/app/app.module.ts

import { MaterialModule } from '@angular/material';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
 declarations: [
   AppComponent
 ],
 imports: [
   BrowserModule,
   FormsModule,
   HttpModule,
   ReactiveFormsModule,
   NgbModule.forRoot(),
   MaterialModule.forRoot()
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { } 

接下来,我们需要选择一个主题。主题是将应用于 Angular Material 组件的一组颜色。在一个主题中,您有以下颜色:

  • 主要调色板包括在所有屏幕和组件上最广泛使用的颜色

  • 强调调色板包括用于浮动操作按钮和交互元素的颜色。

  • 警告调色板包括用于传达错误状态的颜色

  • 前景调色板包括文本和图标的颜色

  • 背景调色板包括用于元素背景的颜色

幸运的是,有默认主题(谷歌在大多数服务中使用的主题),我们可以直接使用。为此,请将以下行添加到您的/src/styles.css文件中:

@import '~@angular/material/core/theming/prebuilt/deeppurple-
     amber.css'; 

在这里,我们使用深紫色主题,这是可用的默认主题之一。您可以在这里看到所有默认主题:node_modules/@angular/material/core/theming/prebuilt

此外,就是这样!您可以运行ng serve来重新编译您的项目,并确认一切都按计划进行。不出所料,目前没有太多要展示的。这是在运行ng serve后拍摄的屏幕截图:

应用程序运行正常!

响应式布局

Material Designs 的一个重要部分是响应式布局,可以适应任何可能的屏幕尺寸。为了实现这一点,我们使用断点宽度:480、600、840、960、1280、1440 和 1600 dp,如以下表格所定义:material.io/guidelines/layout/responsive-ui.html#responsive-ui-breakpoints

断点(dp)手机/平板竖屏手机/平板横屏窗口间距
0小手机超小416
360中等手机超小416
400大手机超小416
480大手机小手机超小416
600小平板中等手机816/24
720大平板大手机816/24
840大平板大手机1216/24
960小平板1224
1024大平板中等1224
1280大平板中等1224
14401224
16001224
1920超大1224

请注意,本章中我们将使用的所有 Material Design 指令已经实现了这些断点。然而,如果您开始主题化(请参阅本章的最后一节)或实现自定义指令,您必须牢记它们。CSS 断点相当容易定义,但可能是繁琐的工作:

@media (min-width: 600dp) {
 .class {
   content: 'Whoa.';
 }
} 

现在,前表的前四列相当不言自明,我们有 dp 中的断点,手持设备/平板电脑纵向,手持设备/平板电脑横向和窗口。然而,最后两个需要一些解释。列栏指示每个 dp 大小均等分屏幕的列数。

间距是每个列之间的空间。这是一个 12 列网格布局:

列(粉色)和间距(蓝色)。

要使用网格系统,您可以将md-columns附加到任何给定标签的类中。例如,<button class="md-2">创建一个宽度为两列的按钮。

要查看您的网站在不同尺寸下的效果,您可以使用 Google Chrome 开发者工具(F12然后CTRL + Shift + M)或material.io/resizer/。请注意,如果您尝试分析的网站将X-Frame-Options设置为DENYmaterial.io将会静默失败。

材料图标

让我们从材料图标开始我们的 Material Design 之旅。材料图标是图标字体,已经创建为在任何分辨率和设备(Web、Android 和 iOS 都得到了官方支持)上工作。

图标传达特殊含义,开发人员倾向于使用相同的图标来传达相同的事物。因此,用户更容易在您的应用程序中找到他们的方式。

有数百个图标可供您使用,每天都会添加新的图标。

以下是一些示例:

折叠图标。

您可以在material.io/icons/上看到所有图标。

由于材料图标是 Material Design 的可选部分(也就是说,您可以使用 Material Design 设计应用程序,例如,使用字体 awesome 图标甚至自定义图标),因此还有另一行代码需要添加到您的代码中。在您的src/index.html文件中,在head部分中添加以下内容:

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" 
    rel="stylesheet"> 

最终的src/index.html将如下所示:

<!doctype html>
<html>
<head>
 <meta charset="utf-8">
 <title>Chap10</title>
 <base href="/">

 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" type="image/x-icon" href="favicon.ico">
 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" 
    rel="stylesheet">
</head>
<body>
 <app-root>Loading...</app-root>
</body>
</html> 

现在,为了查看导入是否成功,我们将在自动生成的应用组件中添加一个图标。在 src/app/app.component.html 中,添加以下内容 <i class="material-icons">cast_connected</i>,使其看起来像这样:

<h1>
 {{title}}

 <i class="material-icons">cast_connected</i>
</h1> 

您的浏览器应该刷新 http://localhost:4200/ 页面并显示 cast_connected 图标:

Cast connected 图标。

正如您所看到的,使用 Material 图标非常简单。第一步是在material.io/icons/上识别您想要使用的一个图标的名称,然后创建一个带有 class="material-icons" 属性的 <i></i> 标签,最后包含您想要的图标名称。以下是一些例子:

  • <i class="material-icons">cast_connected</i>

  • <i class="material-icons">gamepad</i>

  • <i class="material-icons">dock</i>

  • <i class="material-icons">mouse</i>

按钮

除了图标之外,与 Material Design 一起使用的最简单的指令之一是按钮指令。我们可以有一个扁平的、凸起的、圆形的按钮,并且有三种不同的预设颜色:primary、accent 和 warn。以下是一个包含模板的组件,尝试一些可能的组合:


 @Component({
  selector: 'buttons',
  template: `
    <button md-button>FLAT</button>
    <button md-raised-button>RAISED</button> 
    <button md-fab>
        <md-icon>add</md-icon>
    </button>
    <button md-mini-fab>
        <md-icon>add</md-icon>
    </button>
    <button md-raised-button color="primary">PRIMARY</button>
    <button md-raised-button color="accent">ACCENT</button>
    <button md-raised-button color="warn">WARN</button>
  `
 })
 export class ButtonsComponent {
  constructor() { }
 }

结果如下所示:

接下来是:

Primary、Accent 和 Warn 颜色要么在您的 style.scss 中定义为 SCCS 变量,要么在默认的 Material Design 主题中定义,如果您没有覆盖它们。

菜单

在这一部分,我们将对“菜单”指令感兴趣。以下组件创建了一个包含四个元素的菜单。第四个元素被禁用(也就是说,我们无法点击它):

@Component({
 selector: 'menu',
 template: `
 <md-menu>
     <button md-menu-item> Refresh </button>
     <button md-menu-item> Settings </button>
     <button md-menu-item> Help </button>
     <button md-menu-item disabled> Sign Out </button>
 </md-menu>
 `
})
export class MenuComponent {
 constructor() { }
} 

当菜单关闭时,它看起来是这样的:

菜单关闭。

并且在用户点击后打开的版本显示在以下截图中:

菜单已打开。

工具栏

Angular Material Design 的工具栏组件应该按以下方式使用:

<md-toolbar>
 One good looking toolbar
</md-toolbar> 

这将产生以下结果:

基本工具栏。

此外,您可以使用 Angular 的 [color]="primary" | "accent" | "warn" 属性。此外,工具栏可以通过使用 <md-toolbar-row> 标记包含行。

<md-toolbar [color]="accent">
  One good looking toolbar
</md-toolbar>
<md-toolbar [color]="warn">
  <span>First Row</span>

  <md-toolbar-row>
    <span>Second Row</span>
  </md-toolbar-row>

  <md-toolbar-row>
    <span>Third Row</span>
  </md-toolbar-row>
</md-toolbar>
<md-toolbar [color]="primary">
  Another good looking toolbar
</md-toolbar> 

以下将产生三个不同的工具栏,相互叠放。第二个工具栏将由三行组成。

对话框

根据谷歌的定义:对话框通知用户特定任务的信息,可能包含关键信息,需要决策,或涉及多个任务。在 Angular 中使用对话框时,有以下方法:

  • open(component: ComponentType<T>, config: MdDialogConfig): MdDialogRef<T>,创建并打开一个新的对话框,供用户进行交互

  • closeAll(): 用于关闭对话框的 void

然后,对话框本身可以使用四个不同的指令:

  • md-dialog-title将包含对话框的标题,如下所示:<md-dialog-title>我的对话框标题</md-dialog-title>

  • md-dialog-content包含对话框的内容。

例如:<md-dialog-content>我的对话框内容</md-dialog-title>

  • md-dialog-close要添加到按钮中(<button md-dialog-close>关闭</button>)。它使按钮关闭对话框本身。

  • md-dialog-actions用于设置对话框的不同操作,即关闭、放弃、同意等。

在下面的示例中,我们首先有一个草稿组件。草稿组件有一个简单的模板,只包含一个按钮。按钮的click事件调用openDialog方法。对于组件本身的定义,我们有一个接收名为dialogMdDialog的构造函数。openDialog方法有两个回调--一个用于实际打开对话框,另一个用于在对话框关闭时打印包含在result:字符串中的result变量:

@Component({
 selector: 'draft-component',
 template: `
 <button type="button" (click)="openDialog()">Open dialog</button>
 `
})
export class DraftComponent {

 dialogRef: MdDialogRef<DraftDialog>;

 constructor(public dialog: MdDialog) { }

 openDialog() {
   this.dialogRef = this.dialog.open(DraftDialog, {
     disableClose: false
   });

   this.dialogRef.afterClosed().subscribe(result => {
     console.log('result: ' + result);
     this.dialogRef = null;
   });
 }
} 

正如您所看到的,DraftComponent组件的dialogRef属性是通用的。更具体地说,它是DraftDialog类的通用实例。让我们来定义它:

@Component({
 selector: 'draft-dialog',
 template: `
 <md-dialog-content>
   Discard Draft?
 </md-dialog-content>
 <md-dialog-actions>
   <button (click)="dialogRef.close('can
cel')">Cancel</button>
   <button md-dialog-close>Discard</button>
 </md-dialog-actions>
 `
})
export class DraftDialog {
 constructor(public dialogRef: MdDialogRef<DraftDialog>) { }
} 

再次强调,这是一个简单的类。在这里,我们可以看到模板包含了四个可能的指令中的三个。的确,我使用了<md-dialog-content>来定义要显示的对话框内容,<md-dialog-actions>来为对话框的操作按钮提供专用空间,最后,使用md-dialog-close来使“放弃”按钮关闭我的对话框。组件本身只有一个构造函数,定义了public属性:MdDialogRef<DraftDialog>

使用此对话框的最后一步是在我们的NgModule中引用它,就像这样:

@NgModule({
 declarations: [
   ...,
   DraftDialog
 ],
 entryComponents: [
   ...,
   DraftDialog
 ],
 ...
})
export class AppModule { } 

当我们按下按钮时,这是对话框的图像:

草稿对话框。

侧边导航抽屉

侧边导航抽屉在移动设备上非常受欢迎。然而,它们开始出现在完整版本的网站中;因此它们在本章中有所涉及。

侧边导航抽屉可以是这样的:

侧边导航抽屉。

在左侧的浅灰色中,我们有导航抽屉,在调用时会弹出我们的内容。在较深的灰色中,我们有页面的内容。

使用以下组件,我们可以重现本节开头显示的侧边导航:

@Component({
 selector: 'sidenav',
 template: `
   <md-sidenav-container>
   <md-sidenav #side (open)="closeButton.focus()">
      Side Navigation.
     <br>
     <button md-button #closeButton      
         (click)="side.close()">Close</button>
   </md-sidenav>

   My regular content. This will be moved into the proper DOM at 
       runtime.
   <button md-button (click)="side.open()">Open side sidenav</button>

 </md-sidenav-container>
 `
})
export class SideNavComponent {
 constructor() { }
} 

这里唯一有趣的是模板。让我们来分解一下。首先,我们有封闭的<md-sidenav-container>标签,它允许我们为内容定义两个单独的区域。这两个区域分别是md-sidenav和我们页面的实际内容。虽然md-sidenav标签清楚地定义了内容的sidenav部分,但我们页面的其余内容(即实际页面)没有被包含在任何特殊的标签中。页面内容只需在md-sidenav定义之外。我们使用#side属性引用md-sidenav块。作为提醒,向任何 Angular 指令添加#myName会给你一个引用,以便在模板的其余部分中访问它。md-sidenav有一个打开方法,将焦点放在其内部定义的#closeButton上。这个按钮有一个click方法,调用#sideclose方法。最后,在页面内容中,我们有一个按钮,当点击时调用#side.open。除了这两个方法(openclose),md-sidenav指令还有一个toggle方法,用于切换sidenav(即opened = !opened)。

主题化

现在,我们可以描述 Angular Material Design 中每个可用组件。然而,它们有很多,它们的用途都不复杂。在我撰写本章时,以下是支持的指令列表:

  • 按钮

  • 卡片

  • 复选框

  • 单选按钮

  • 输入

  • 侧边栏

  • 工具栏

  • 列表

  • 网格

  • 图标

  • 进度

  • 选项卡

  • 滑动

  • 滑块

  • 菜单

  • 工具提示

  • 涟漪

  • 对话框

  • 消息框

在接下来的几个月里,将会添加更多的指令。你可以在这里找到它们:github.com/angular/material2

不用说,我们在指令方面已经覆盖了。尽管有如此广泛的可能性,我们可以通过创建自定义主题进一步定制 Angular 的 Material Design。在 Angular Material 中,主题是通过组合多个调色板创建的。特别是,主题包括:

  • 主要调色板由在所有屏幕和组件上广泛使用的颜色组成

  • 强调调色板由用于浮动操作按钮和交互元素的颜色组成

  • 警告调色板由用于传达错误状态的颜色组成

  • 前景调色板由用于文本和图标的颜色组成

  • 背景调色板由用于元素背景的颜色组成

以下是一个自定义主题的示例:

//src/styles.scss

@import '~https://fonts.googleapis.com/icon?family=Material+Icons';
@import '~@angular/material/core/theming/all-theme';
// Plus imports for other components in your app.

// Include the base styles for Angular Material core. We include this here so that you only
// have to load a single css file for Angular Material in your app.
@include md-core();

// Define the palettes for your theme using the Material Design 
   palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a 
  default, lighter, and darker
// hue.
  $candy-app-primary: md-palette($md-indigo);
  $candy-app-accent:  md-palette($md-pink, A200, A100, A400);

// The warn palette is optional (defaults to red).
   $candy-app-warn:    md-palette($md-red);

// Create the theme object (a Sass map containing all of the palettes).
  $candy-app-theme: md-light-theme($candy-app-primary, $candy-app-  
   accent, $candy-app-warn);

// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each 
   component
// that you are using.
@include angular-material-theme($candy-app-theme); 

因此,我们已经学会了为 Material Design 创建自定义主题。

总结

在本章中,我们通过使用 Angular/Material2 模块了解了 Material Design 和响应式设计。我们看到了一些最常用的指令,如buttonsiconsdialogssidenav。此外,我们还利用了 Angular/Material2 的主题能力来定制 Material Design。

在第十五章中,将 Bootstrap 与 Angular 应用程序集成,我们将看到如何通过使用 Bootstrap(由 Twitter 提供)而不是 Material Design(由 Google 提供)来驱动我们的 Angular2 应用程序的设计。

第十章:实现 Angular 管道

在本章中,您将学习关于 Angular 管道。将 Angular 管道视为过滤器的现代化版本,包括帮助我们在模板中格式化值的函数。Angular 中的管道基本上是 Angular v1 中过滤器的扩展。我们可以在模板中轻松使用许多有用的内置管道。您将学习内置管道,我们还将创建自定义用户定义的管道。

在本章结束时,您将学习并实现以下内容:

  • 介绍 Angular 管道

  • 定义和实现管道

  • 了解各种内置管道

  • DatePipe

  • DecimalPipe

  • CurrencyPipe

  • LowerCasePipe 和 UpperCasePipe

  • JSON 管道

  • SlicePipe

  • async 管道

  • 学习实现自定义用户定义的管道

  • 为管道参数化

  • 链接管道

  • 了解纯管道和不纯管道

Angular 管道-概述

管道允许我们在模板视图中显示值之前格式化值。例如,在大多数现代应用程序中,我们希望显示诸如今天、明天等术语,而不是系统日期格式,例如 2017 年 4 月 13 日 08:00。让我们看看更多现实世界的场景。

您希望应用程序中的提示文本始终为小写吗?没问题;定义并使用LowercasePipe。在天气应用程序中,如果您希望显示月份名称为 MAR 或 APR 而不是其全名,请使用DatePipe

很酷,对吧?你明白了。管道帮助您添加业务规则,因此您可以在模板中实际显示数据之前转换数据。

与 Angular 1.x 过滤器建立联系的一个好方法是通过 Angular 管道,但管道不仅仅是过滤。

我们已经使用了 Angular 路由器来定义路由路径,因此我们在一个页面中拥有所有管道的功能;您可以在相同或不同的应用程序中创建它。随意发挥您的创造力。

在 Angular 1.x 中,我们有过滤器--管道是过滤器的替代品。

在下一节中,您将学习如何定义和使用 Angular 管道。

定义管道

管道运算符用管道符号(|)定义,后跟管道的名称:

{{ appvalue  | pipename }}

以下是一个简单的lowercase管道的示例:

{{"Sridhar Rao"  |  lowercase}} 

在上述代码中,我们使用lowercase管道将文本转换为小写。

现在,让我们编写一个使用lowercase管道示例的示例Component

@Component({
 selector: 'demo-pipe',
 template: `
 Author name is {{authorName | lowercase}}
 `
})
export class DemoPipeComponent {
 authorName = 'Sridhar Rao';
}

让我们详细分析上述代码:

  • 我们定义了一个DemoPipeComponent组件类

  • 我们创建了一个字符串变量authorName,并赋予了值'Sridhar Rao'

  • 在模板视图中,我们显示了authorName;然而,在将其打印到 UI 之前,我们使用了lowercase管道进行转换

运行上述代码,您应该看到以下输出:

干得好!在前面的例子中,我们使用了内置管道。在接下来的部分,您将学习更多关于内置管道,并创建一些自定义管道。

请注意,管道运算符仅在模板中起作用,而不在控制器内部。

内置管道

Angular 管道是 Angular 1.x 过滤器的现代化版本。Angular 带有许多预定义的内置管道。我们可以直接在视图中使用它们,并在运行时转换数据。

以下是 Angular 内置支持的所有管道的列表:

  • DatePipe

  • DecimalPipe

  • CurrencyPipe

  • LowercasePipe 和 UppercasePipe

  • JSON 管道

  • SlicePipe

  • 异步管道

在接下来的部分,让我们实现并学习更多关于各种管道,并看到它们的实际应用。

DatePipe

DatePipe,顾名思义,允许我们格式化或转换与日期相关的值。DatePipe 也可以根据运行时传递的参数以不同格式转换值。

一般语法如下代码片段所示:

{{today | date}} // prints today's date and time
{{ today | date:'MM-dd-yyyy' }} //prints only Month days and year
{{ today | date:'medium' }} 
{{ today | date:'shortTime' }} // prints short format

让我们详细分析前面的代码片段:

  • 如前一节所述,一般语法是变量后跟着一个(|)管道运算符,然后是管道运算符的名称

  • 我们使用 DatePipe 来转换today变量

  • 此外,在前面的例子中,您会注意到我们向管道运算符传递了一些参数;我们将在下一节中介绍向管道传递参数

现在,让我们创建一个完整的DatePipe组件示例;以下是实现DatePipe组件的代码片段:

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

@Component({
 template: `
 <h5>Built-In Pipes</h5>
 <ol>
 <li>
 <strong class="packtHeading">DatePipe example 1</strong>
 <p>Today is {{today | date}}
 </li>
 <li>
 <strong class="packtHeading">DatePipe example 2</strong>
 <p>{{ today | date:'MM-dd-yyyy' }} 
 <p>{{ today | date:'medium' }}
 <p>{{ today | date:'shortTime' }} 
 </li>
 </ol>
 `,
})
export class PipeComponent {
 today = new Date();
}

让我们详细分析前面的代码片段:

  1. 我们创建了一个PipeComponent组件类。

  2. 我们定义了一个today变量。

  3. 在视图中,我们根据不同的参数将变量的值转换为各种表达式。

现在运行应用程序,我们应该看到以下输出:

您在本节中学习了DatePipe。在接下来的部分,您将继续学习和实现其他内置管道,并创建一些自定义用户定义的管道。

DecimalPipe

在本节中,您将了解另一个内置管道--DecimalPipe。

DecimalPipe 允许我们根据区域规则格式化数字。 DecimalPipe 也可以用于以不同格式转换数字。

一般的语法如下:

appExpression | number [:digitInfo]

在上述代码片段中,我们使用了数字管道,可以选择性地传递参数。

让我们看看如何创建一个实现小数点的DatePipe,以下是相同的示例代码:

import { Component } from '@angular/core';
@Component({
 template: `
  <h5>Built-In Pipes</h5>
 <ol>
<li>
<strong class="packtHeading">DecimalPipe example</strong>
 <p>state_tax (.5-5): {{state_tax | number:'.5-5'}}</p>
 <p>state_tax (2.10-10): {{state_tax | number:'2.3-3'}}</p>
 </li>
 </ol>
 `,
})
export class PipeComponent {
 state_tax: number = 5.1445;
}

让我们详细分析上述代码片段:

  1. 我们定义了一个组件类,即PipeComponent

  2. 我们定义了一个state_tax变量。

  3. 然后我们在视图中转换了state_tax

  4. 第一个管道操作符告诉表达式将小数打印到小数点后五位。

  5. 第二个管道操作符告诉表达式将值打印到小数点后三位。

上述管道组件示例的输出如下:

毫无疑问,数字管道是各种应用程序中最有用和常用的管道之一。我们可以转换数字值,特别是处理小数和浮点数。

CurrencyPipe

对于希望迎合多国地理位置的应用程序,我们需要显示特定国家的代码及其相应的货币值--这就是CurrencyPipe派上用场的地方。

CurrencyPipe操作符用于在数字值前附加国家代码或货币符号。

看一下实现CurrencyPipe操作符的代码片段:

{{ value | currency:'USD' }}

Expenses in INR: {{ expenses | currency:'INR' }}

让我们详细分析上述代码片段:

  1. 第一行代码显示了编写CurrencyPipe的一般语法。

  2. 第二行显示了货币的语法,我们用它来转换expenses的值,并在其后附加了印度货币符号。

现在我们知道如何使用CurrencyPipe操作符,让我们组合一个示例来显示多种货币国家格式;以下是实现CurrencyPipe操作符的完整组件类:

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

@Component({
 selector: 'currency-pipe',
 template: `
 <h5>CurrencyPipe Example</h5>
 <ol>
 <li>
 <p>Salary in USD: {{ salary | currency:'USD':true }}</p>
 <p>Expenses in INR: {{ expenses | currency:'INR':false }}</p>
 </li>
 </ol>
 `
})
export class CurrencyPipeComponent {
 salary: number = 2500;
 expenses: number = 1500;
}

让我们详细分析上述代码:

  1. 我们创建了一个组件类CurrencyPipeComponent,并声明了几个变量,即salaryexpenses

  2. 在组件模板中,我们通过添加国家货币详情来转换变量的显示。

  3. 在第一个管道操作符中,我们使用了'currency: USD',这将在变量之前附加($)美元符号。

  4. 在第二个管道操作符中,我们使用了'currency : 'INR':false',这将添加货币代码,false将告诉它不要打印符号。

现在,启动应用程序,我们应该看到以下输出:

在本节中,我们讨论并实现了CurrencyPipe。在接下来的几节中,我们将继续探索和学习其他内置管道以及更多内容。

LowerCasePipe 和 UpperCasePipe

LowerCasePipe 和 UpperCasePipe,顾名思义,分别用于将文本转换为小写和大写。

看一下以下代码片段:

Author is Lowercase {{authorName | lowercase }}
Author in Uppercase is {{authorName | uppercase }}

让我们详细分析前面的代码:

  1. 第一行代码使用lowercase管道将authorName的值转换为小写。

  2. 第二行代码使用uppercase管道将authorName的值转换为大写。

现在我们已经看到如何定义小写和大写管道,是时候创建一个完整的组件示例了,该示例实现了管道以显示作者姓名的小写和大写形式。

看一下以下代码片段:

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

@Component({
 selector: 'textcase-pipe',
 template: `
 <h5>Built-In LowercasPipe and UppercasePipe</h5>
 <ol>
 <li>
 <strong>LowercasePipe example</strong>
 <p>Author in lowercase is {{authorName | lowercase}}
 </li>
 <li>
 <strong>UpperCasePipe example</strong>
 <p>Author in uppercase is {{authorName | uppercase}}
 </li>
 </ol>
 `
})
export class TextCasePipeComponent {
 authorName = "Sridhar Rao";
}

让我们详细分析前面的代码:

  1. 我们创建了一个组件类,TextCasePipeComponent,并定义了一个authorName变量。

  2. 在组件视图中,我们使用了lowercaseuppercase管道。

  3. 第一个管道将变量的值转换为小写文本。

  4. 第二个管道将变量的值转换为大写文本。

运行应用程序,我们应该看到以下输出:

在本节中,您学会了如何使用lowercaseuppercase管道来转换值。

JSON Pipe

类似于 Angular 1.x 中的 JSON 过滤器,我们有 JSON 管道,它可以帮助我们将字符串转换为 JSON 格式的字符串。

在小写或大写管道中,我们转换了字符串;使用 JSON 管道,我们可以将字符串转换并显示为 JSON 格式的字符串。

通用的语法如下代码片段所示:

<pre>{{ myObj | json }}</pre>

现在,让我们使用前面的语法并创建一个完整的Component示例,其中使用了 JSON Pipe:

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

@Component({ 
 template: `
 <h5>Author Page</h5>
 <pre>{{ authorObj | json }}</pre>
 `
})
export class JSONPipeComponent {
 authorObj: any; 
 constructor() {
 this.authorObj = {
 name: 'Sridhar Rao',
 website: 'http://packtpub.com',
 Books: 'Mastering Angular2'
 };
 }
}

让我们详细分析前面的代码:

  1. 我们创建了一个组件类,JSONPipeComponentauthorObj,并将 JSON 字符串赋给了这个变量。

  2. 在组件模板视图中,我们转换并显示了 JSON 字符串。

运行应用程序,我们应该看到以下输出:

JSON 很快就成为了 Web 应用程序之间集成服务和客户端技术的事实标准。因此,每当我们需要将值转换为视图中的 JSON 结构时,JSON 管道都非常方便。

SlicePipe

SlicePipe 与数组切片 JavaScript 函数非常相似。Slice 管道从字符串中提取两个指定索引之间的字符,并返回新的子字符串。

定义 SlicePipe 的一般语法如下:

{{email_id | slice:0:4 }}

在前面的代码片段中,我们正在对电子邮件地址进行切片,以仅显示变量值email_id的前四个字符。

既然我们知道如何使用 SlicePipe,让我们在组件中将其放在一起。

以下是实现 SlicePipe 的完整代码片段:

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

@Component({
 selector: 'slice-pipe',
 template: `
 <h5>Built-In Slice Pipe</h5>
 <ol>
 <li>
 <strong>Original string</strong>
 <p> Email Id is {{ emailAddress }}
 </li>
 <li>
 <strong>SlicePipe example</strong>
 <p>Sliced Email Id is {{emailAddress | slice : 0: 4}}
 </li>
 </ol>
 `
})
export class SlicePipeComponent {
 emailAddress = "test@packtpub.com";
}

让我们详细分析前面的代码片段:

  1. 我们创建了一个SlicePipeComponent类。

  2. 我们定义了一个字符串变量emailAddress并为其赋值test@packtpub.com

  3. 然后,我们将 SlicePipe 应用于{{emailAddress | slice : 0: 4}}变量。

  4. 我们从0位置开始获取子字符串,并从变量值emailAddress中获取4个字符。

运行应用程序,我们应该看到以下输出:

SlicePipe 肯定是一个非常有用的内置管道,特别是处理字符串或子字符串。

异步管道

异步管道允许我们直接将 promise 或 observable 映射到我们的模板视图中。为了更好地理解异步管道,让我先介绍一下 observable。

Observables 是 Angular 可注入的服务,可用于将数据流式传输到应用程序中的多个部分。在下面的代码片段中,我们使用async管道作为一个 promise 来解析返回的作者列表:

<ul id="author-list">
 <li *ngFor="let author of authors | async" >
 <!-- loop the object here -->
 </li>
</ul>

async管道现在订阅Observable(作者)并检索最后一个值。

让我们看一下如何使用async管道作为PromiseObservable的示例。

在我们的app.component.ts文件中添加以下代码行:

 getAuthorDetails(): Observable<Author[]> {
  return this.http.get(this.url).map((res: Response) => res.json());
 }

 getAuthorList(): Promise<Author[]> {
  return this.http.get(this.url).toPromise().then((res: Response) => 
   res.json());
 }

让我们详细分析前面的代码片段:

  1. 我们创建了一个getAuthorDetails方法,并附加了一个相同的 observable。该方法将返回来自url的响应,这是一个 JSON 输出。

  2. getAuthorList方法中,我们绑定了一个需要在通过http请求调用的url返回的输出中解析或拒绝的 promise。

在本节中,我们已经看到了async管道的工作原理。您会发现它与处理服务非常相似。我们可以映射一个 promise 或一个 observable,并将结果映射到模板上。

参数化管道

管道也可以带参数。我们可以在管道后面传递参数。参数在管道后用冒号符号(:)分隔:

{{appValue  |  Pipe1: parameter1: parameter2 }}

让我们快速构建一个简单的管道示例,看看它的运行情况。以下是带有MM-dd-yyyy参数的DatePipe的示例:

{{today | date:'MM-dd-yyyy' }} 

另一个带参数的管道示例如下:

{{salary | currency:'USD':true}}

让我们详细分析前面的代码片段:

  1. 我们向CurrencyPipe传递了 USD 作为参数,这将告诉管道显示货币代码,例如美元的USD和欧元的EUR

  2. true参数表示显示货币符号($)。默认情况下,它设置为 false。

让我们通过组件的完整代码来看它们的运行情况:

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

@Component({
 template: `
 <h5>Parametrizing pipes</h5>

 <p>Date with parameters {{ today | date:'MM-dd-yyyy' }} 
 <p>Salary in USD: {{salary | currency:'USD':true}}</p>
 `,
})
export class ParamPipeComponent {
 today = new Date();
 salary: number = 1200;
}

在前面的代码片段中,我们创建了一个ParamPipeComponent类,并定义了todaysalary变量。

Component模板视图中,我们为DatePipe传递了date:'MM-dd-yyyy'参数,为CurrencyPipe传递了currency:'USD' :true参数。

以下是前面代码的输出:

在前面的示例中,我们传递了自定义参数,如currencydate格式,给管道,并相应地查看了输出。

在大多数应用用例中,我们需要向管道传递参数,以根据业务逻辑转换值。在本节中,我们重点介绍了通过传递值来对管道进行参数化。

到目前为止,我们一直在使用内置管道并向管道传递参数。

在接下来的章节中,您将学习如何链接管道、创建自定义管道,以及向自定义用户定义的管道传递参数。

链式管道

我们可以将多个管道链接在一起。这在我们需要关联多个需要应用的管道,并且最终输出将被所有应用的管道转换的情况下特别有帮助。

工作流或链将被触发,并依次应用管道。链管道语法的示例如下:

{{today | date | uppercase | slice:0:4}}

我们在前面的代码中应用了两个链式管道。首先,DatePipe应用于today变量,然后立即应用uppercase管道。以下是ChainPipeComponent的整个代码片段:

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

@Component({
 template: `
 <h5>Chain Pipes</h5>
 <p>Month is {{today | date | uppercase | slice:0:4}}
 `,
})
export class ChainPipeComponent {
 today = new Date();
}

我们使用了 slice 来仅显示月份的前四个字符。以下截图显示了前面组件的输出:

应用链式管道时需要记住的一些关键事项如下:

  • 执行顺序是从左到右

  • 管道是依次应用的。

在本节中,您了解了如何在我们的应用程序中将多个管道链接在一起。在下一节中,您将详细了解如何创建自己的自定义管道。

创建自定义管道

到目前为止,一切都很好。管道确实给我们留下了深刻的印象,但等等,我们还可以用管道做更棒的事情。内置管道,正如您所见,非常有限且少。我们当然需要创建自己的自定义管道,以满足我们应用程序的功能。

在本节中,您将学习如何为我们的应用程序创建自定义管道。

在这个例子中,我们将构建一个管道,它将是一个税收计算器。我们传递产品的价格,并使用管道功能自动计算并显示销售税。神奇,对吧?

要创建自定义管道,我们需要执行以下步骤:

  1. 创建一个模板来应用到管道上(在我们的例子中,它是updateTaxPipe)。

  2. 创建一个管道文件,即update-tax.pipe.ts

  3. 每个管道文件都必须从 Angular 核心中导入管道。

  4. 定义管道元数据。

  5. 创建Component类。它应该有transform函数,其中包含管道应该执行的业务逻辑。

在下面的代码片段中,我们正在定义一个名为UpdateTaxPipe的自定义管道,它将接受一个percentage参数,并进行销售税计算并在我们的模板中显示:

{{ productPrice | UpdateTaxPipe: percentage }}

让我们创建我们的update-tax.pipe.ts文件:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
 name : "UpdateTaxPipe"
})

export class UpdateTaxPipe implements PipeTransform{
 transform(value:number, taxVal: number):number{
 return (value*taxVal)/100;
 }
}

让我们详细分析前面的代码片段:

  1. 为了告诉 Angular 这是一个管道,我们应用了@Pipe装饰器,它是从核心 Angular 库中导入的。

  2. 我们创建了一个自定义管道,名为UpdateTaxPipe,使用了name管道元数据。

  3. 我们创建了一个transform方法,这对于管道是必需的,并在方法内定义了我们的业务逻辑和规则。

  4. 我们向transform方法传递了两个参数,它返回了更新后的值。

无论我们是否包括接口 PipeTransform,Angular 都会寻找并执行transform方法。

运行应用程序,我们应该看到如下截图所示的输出:

在本节中,您学习了如何创建自定义管道。创建用户定义的管道非常简单和容易。自定义管道确实帮助我们轻松地集成应用程序的业务逻辑。

尝试创建自定义管道,可以适应一次编写,多次使用逻辑,也可以在许多组件视图中使用;例如,验证电话号码、地址等。

纯管道和非纯管道

管道还接受一个名为 Pure 的元数据。管道有两种状态:

  • 纯管道

  • 非纯管道

纯管道

纯管道只有在输入参数的值发生变化时才会执行。它不会记住或追踪任何先前的值或状态。Angular 内置管道都是pure管道。

到目前为止我们看到的所有管道示例都是纯管道。

非纯管道

无论值或参数是否改变,非纯管道都会在每次变更检测周期中调用。为了使用非纯管道,我们应该将管道修饰符pure设置为false

默认情况下,所有管道修饰符的pure都设置为true

将管道修饰符的值设置为pure将检查管道的输出,无论其值是否改变,都会保持更新管道提供的值。

定义非纯管道与创建任何自定义用户定义管道相同,唯一的区别在于在@Pipe修饰符中,我们将通过将值设置为false来明确指定管道为非纯的。

以下是通过将管道的值设置为 false 来定义非纯管道的语法:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'authorName'
  pure: false
})

在本节中,您了解了不同类型的 Angular 管道,即纯管道和非纯管道。只有在输入组件的值发生变化时才会调用纯管道。无论值是否改变,非纯管道都会在每次变更检测时调用。

摘要

在本章中,您了解了关于 Angular 管道的一切。Angular 管道在转换视图模板中的数据方面非常有用。Angular 管道是 Angular 1.x 中可用的过滤器的现代化版本。

我们可以在模板中轻松使用许多有用的内置管道操作符。在本章中,您了解了内置管道以及创建自定义用户定义管道。

在处理数字时,我们可以使用DatePipeDecimalPipeCurrencyPipe。在专门处理字符串时,我们可以始终使用SlicePipeLowercasePipeUppercasePipe

当我们主要处理服务器端响应或进行异步调用并处理响应时,我们可以使用JSONPipeasyncPipe。我们还涵盖了向管道传递参数,并根据应用程序的需要进行定制。

我们探讨了如何创建和实现自定义用户定义的管道,这些管道还可以接受参数,以根据我们应用程序的需求更好地定制它们。

所以继续,用管道转换你的视图。

在下一章中,您将学习如何实现 Angular 服务。您将学习有关服务和工厂的知识,创建 Angular 服务,使用服务从组件中访问数据以及创建异步服务。

第十一章:实现 Angular 服务

服务在任何 Angular 应用程序中都扮演着重要的角色。我们可以通过充分利用 Angular 中的许多内置服务来设计我们自己的 Angular 服务。在本章中,我们将讨论如何做到这一点,以便您了解如何创建和管理 Angular 服务。

在本章中,我们将涵盖以下主题:

  • 为什么要使用服务或工厂?

  • 创建服务

  • 使用服务从组件中访问数据

  • 创建异步服务

为什么要使用服务或工厂?

我们已经讨论了单向数据绑定、双向数据绑定以及组件之间的数据共享。我们可能已经定义了非常清晰的视图并实现了整洁的组件,但是业务逻辑和数据获取/存储逻辑必须存在于某个地方。构建出色的 Angular 应用程序来自于充分利用内置服务。Angular 框架包括帮助您进行网络、缓存、日志记录、承诺等方面的服务。

编写我们自己的服务或工厂有助于实现代码的可重用性,并使我们能够在应用程序块(如组件、指令等)之间共享特定于应用程序的逻辑。将特定于应用程序的逻辑组织到服务或工厂中会导致更清晰、更明确定义的组件,并帮助您以更易维护的代码组织项目。

在 AngularJS 中,我们为此目的实现服务或工厂。服务是在运行时使用 new 关键字调用的,比如构造函数。以下代码片段显示了服务实现的 AngularJS 代码:

function MovieService($http) {   
  this.getMovieList = function   getMovieList() {   
    return $http.get('/api/movies');   
  };   
}   
angular.module('moviedb').service('MovieService',   MovieService);   

MovieService函数可以注入到任何需要从 API 获取电影列表的控制器中。

在 Angular 中,可以使用工厂来实现相同的功能,并具有额外的功能。工厂是处理创建对象的一种设计模式。我们可以从工厂返回新的类、函数或闭包。与服务类似,工厂也可以注入到控制器中。以下代码片段显示了工厂实现的 AngularJS 代码:

function MovieService($http) {   
  return {   
    getMovieList: function() {   
         return $http.get('/api/movies');   
    }   
  };   
}   
angular.module('moviedb').factory('MovieService',   MovieService);   

服务和工厂都可以注入到控制器中,并且可以调用getMovieList函数,如下所示:

function MovieController(MovieService   service) {   
  service.getMovieList().then(function   (response) {   
      // manage response   
    });   
}   
angular.module('moviedb').controller('MovieController',   
        MovieController);   

虽然工厂是灵活的,但服务是使迁移到 ES6 更容易的最佳选择。使用服务时,ES5 中的构造函数可以在迁移到 ES6 的过程中顺利替换为 ES6 类。我们可以将MovieService服务重写为 ES6 如下:

class MovieService {
 getMovieList() {
  return $http.get('/api/movies');
 }
}
app.service('MovieService', MovieService);

服务是用户定义的类,用于解决特定目的,并可以注入到组件中。Angular 建议在组件中只包含与视图相关的代码,以丰富 Angular 应用程序中的 UI/UX。组件是服务的消费者,它们作为应用程序数据的来源和业务逻辑的库。保持组件清晰并注入服务使我们能够针对模拟服务测试组件:

创建一个服务

应用程序特定或业务逻辑函数,如持久化应用程序数据、记录错误、文件存储等,应该委托给服务,组件应该消费相应的服务来处理适当的业务或应用程序特定逻辑:

让我们创建一个简单的名为BookService的服务,处理获取源中可用的书籍集合。源可以是从 Web API 服务返回的数据或 JSON 文件。

首先,让我们创建一个Book模型来持久化领域对象值。下面显示了Book类的代码片段:

export class Book { 
  id: number; 
  title: string; 
  author: string; 
  publisher: string; 
} 

前面的代码片段显示了一个Book的 TypeScript 类,其中包括idtitleauthorpublisher等属性。现在让我们创建一个名为BookService的服务,处理与Book相关的操作:

import { Injectable } from '@angular/core';
import {Book} from './book';
@Injectable()
export class BookService {
  getBooks() {
  var books: Book[] = [
    { "id": 1, "title": "ASP.NET Web API Security Essentials", author:   
         "Rajesh Gunasundaram", publisher: "Packt Publishing" },
    { "id": 2, "title": "Learning Angular for .Net Developers", author: 
         "Rajesh Gunasundaram", publisher: "Packt Publishing" },
    { "id": 3, "title": "Mastering Angular", author: "Rajesh 
         Gunasundaram", publisher: "Packt Publishing" },
   ];
  return books;
  }
}

在这里,我们首先导入了Book模型类。然后,我们定义了BookService类,其中包含一个getBooks方法,返回书籍的集合。

现在我们需要一个组件来注入BookService并消费。让我们创建一个BookListComponent,通过调用BookServicegetBooks方法来检索书籍列表。以下代码片段显示了BookListComponent类:

import { Component, OnInit } from '@angular/core';
import { Book } from './book';
import { BookService } from './book.service';
@Component({
   selector: 'book-list',
   template: `
   <div *ngFor="let book of books">
   {{book.id}} - {{book.title}}<br/>
   Author: {{book.author}}<br/>
   Publication: {{book.publisher}} 
   </div>
    `,
   providers: [BookService]
  })
 export class BookListComponent implements OnInit {
   books: Array<Book>;
   constructor(private bookService: BookService) { }
   ngOnInit() {   
       this.books = this.bookService.getBooks();
      }
   }

在这里,我们首先从@angular/core中导入ComponentOnInit,然后导入Book模型类和BookService类。然后我们用@Component属性对BookListComponent类进行了注释,以及选择器和模板等元数据信息。BookListComponent类定义了一个Book数组的books变量和一个构造函数,它被注入了BookService。请注意,BookListComponent实现了OnInit生命周期钩子,并且它通过使用注入到构造函数中的BookService实例来调用BookService类的getBooks方法。getBooks返回的书籍列表被赋给了BookListComponent类的books变量。

现在让我们创建一个根组件AppComponent。将BookListComponent作为指令传递,并将BookService作为提供者。以下是AppComponent的代码片段:

import { Component } from '@angular/core';   
import { BookService } from './book.service';   

@Component({   
  selector: 'my-books',   
  template: '   
    <h2>Book Library</h2>   
  <book-list></book-list>   
  '   
})   
export class AppComponent { }   

在这里,我们首先从@angular/core中导入ComponentBookListComponentBookService。然后我们用@Component属性对AppComponent进行了注释,以及选择器和模板等元数据。请注意,模板中有一个特殊的 HTML 标签<book-list/>。在某个地方,我们需要指示 Angular 初始化BooklistComponent并相应地渲染视图。我们还需要告诉 Angular,AppComponent是根组件,通过引导它来实现这一点。我们可以通过为我们的 Angular 应用程序创建一个入口点来实现这一点。

创建一个名为AppModule的类,并用NgModule进行注释(app.module.ts)。这指示 Angular 模块,这个类是应用程序的入口点。这里给出了AppModule的代码片段:

import { NgModule }          from '@angular/core';   
import { BrowserModule }  from '@angular/platform-browser';   
import { AppComponent }   from './app.component';   
import { BookListComponent }  from './book-list.component';   

@NgModule({   
  imports:        [ BrowserModule ],   
  declarations: [ AppComponent,   BooklistComponent ],   
  bootstrap:     [ AppComponent ]   
})   
export class AppModule { }   

在这里,我们首先从 Angular 核心中导入NgModule。然后我们从 Angular 平台浏览器中导入BrowserModule,因为我们的应用程序在 Web 浏览器上运行。然后我们导入应用程序组件,比如AppComponent,它是一个引导根组件,以及BooklistComponent,导入并添加到声明中。请注意,AppModule被装饰为NgModule,同时具有元数据,如导入、声明和引导。

现在让我们创建一个index.html页面,其中包含以下代码片段:

<!DOCTYPE html>   
<html>   
  <head>   
    <base href="/">   
    <title>Book   Library</title>   
    <meta charset="UTF-8">   
    <meta name="viewport"   content="width=device-width, initial-
          scale=1">   
  </head>   
  <body>   
    <h1>TodoList Angular app for   Packt Publishing...</h1>

    <my-books>Loading...</my-books>   
  </body>   
</html>   

在这里,我们没有引用任何必要的库来自node_modules,因为它们将由 Webpack 加载。Webpack 是一个用于捆绑资源并从服务器提供给浏览器的工具。Webpack 是 systemJS 的推荐替代方案。

使用服务从组件中访问数据

随着 Angular 应用程序的发展,我们不断引入更多的组件,这些组件将处理应用程序的核心数据。因此,我们可能会写重复的代码来访问数据。然而,我们可以通过引入可重用的数据服务来避免编写冗余的代码。需要数据的组件可以注入数据服务,并且可以用来访问数据。通过这种方式,我们可以重用逻辑,编写更少的代码,并在设计组件时有更多的分离。

我们将使用 Angular 的HttpModule,它作为一个npm包进行发布。为了在我们的应用程序中使用HttpModule,我们需要从@Angular/http导入HttpModule,并且 HTTP 服务应该被注入到控制器或应用程序服务的构造函数中。

实施服务

应用程序可以在组件之间共享数据。考虑一个电影数据库应用程序,其中Movies列表或单个Movie对象将在组件之间共享。我们需要一个服务来在任何组件请求时提供Movies列表或单个Movie对象。

首先,让我们使用 Angular CLI 创建一个电影服务。在命令提示符中执行以下命令以生成movie.service的样板代码:

e:\Explore\packt\MovieDB>ng generate   service Movie   
installing service   
  create src\app\movie.service.spec.ts   
  create src\app\movie.service.ts   

e:\Explore\packt\MovieDB>   

在这里,Angular CLI 创建了两个文件,即movie.service.tsmovie.service.spec.ts。生成的movie.service.ts的样板代码如下所示:

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

@Injectable()   
export class MovieService {   

  constructor() { }   

}   

请注意,MovieService类被装饰为@Injectable属性,以便依赖注入来实例化并将此服务注入到任何需要它的组件中。我们通过从 Angular 核心导入它,使这个Injectable函数可用。

接下来,我们需要向生成的MovieService添加getMovies函数。将getMovies()函数引入到MovieService类中如下:

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

@Injectable()   
export class MovieService {   

  constructor() { }   
  getMovies(): void {}   
}   

请注意,我们现在将返回类型设置为 void,但是当我们进行进一步实现时,我们需要进行更改。

我们需要引入一个领域模型,Movie,来表示整个应用程序中的电影。让我们使用 Angular CLI 生成Movie类的样板代码如下:

e:\Explore\packt\MovieDB>ng generate   class Movie   
installing class   
  create src\app\movie.spec.ts   
  create src\app\movie.ts   

e:\Explore\packt\MovieDB>   

在这里,这个命令创建了两个文件,分别是movie.tsmovie.spec.ts。实际上,在领域模式下,我们可能不会编写任何测试方法来断言它,所以你可以安全地删除movie.spec.ts。生成的movie.ts的代码片段如下所示:

export class Movie {   
}   

让我们添加一些属性来使其代表电影的特征。代码如下所示:

export class Movie {   

   public constructor(   
         private _movie_id:number,   
         private _title: string,   
         private _phase: string,   
         private _category_name: string,   
         private _release_year: number,   
         private _running_time: number,   
         private _rating_name: string,   
         private _disc_format_name:   string,   
         private _number_discs: number,   
         private _viewing_format_name:   string,   
         private _aspect_ratio_name:   string,   
         private _status: string,   
         private _release_date: string,   
         private _budget: number,   
         private _gross: number,   
         private _time_stamp:Date){   
   }   

   public toString = () : string => {   

         return `Movie (movie_id:   ${this._movie_id},   
         title: ${this._title},   
         phase: ${this._phase},   
         category_name:   ${this._category_name},   
         release_year:   ${this._release_year},   
         running_time: ${this._running_time},   
         rating_name:   ${this._rating_name},   
         disc_format_name:   ${this._disc_format_name},   
          number_discs:   ${this._number_discs},   
         viewing_format_name:   ${this._viewing_format_name},   
         aspect_ratio_name: ${this._aspect_ratio_name},   
         status: ${this._status},   
         release_date:   ${this._release_date},   
         budget: ${this._budget},   
         gross: ${this._gross},   
         time_stamp:   ${this._time_stamp})`;   

   }   
}   

我们已经准备好领域模型。现在让我们更新MovieServicegetMovies()函数的返回类型如下:

getMovies(): Movie[] {   
    let movies: Movie[] = [   
          {   
               "movie_id" : 1,   
               "title" : "Iron   Man",   
               "phase" : "Phase   One: Avengers Assembled",   
               "category_name"   : "Action",   
               "release_year" :   2015,   
               "running_time" :   126,   
               "rating_name" : "PG-13",   
               "disc_format_name"   : "Blu-ray",   
               "number_discs" :   1,   
               "viewing_format_name"   : "Widescreen",   
               "aspect_ratio_name"   : " 2.35:1",   
               "status" : 1,   
               "release_date" :   "May 2, 2008",   
               "budget" : "140,000,000",   
               "gross" : "318,298,180",   
               "time_stamp" : "2015-05-03"   
         },   
          {   
               "movie_id" : 2,   
               "title" : "Spiderman",   
               "phase" : "Phase   One",   
               "category_name"   : "Action",   
               "release_year" :   2014,   
               "running_time" :   126,   
               "rating_name" : "PG-13",   
               "disc_format_name"   : "Blu-ray",   
               "number_discs" :   1,   
               "viewing_format_name"   : "Widescreen",   
               "aspect_ratio_name"   : " 2.35:1",   
               "status" : 1,   
               "release_date" :   "May 2, 2008",   
               "budget" : "140,000,000",   
               "gross" : "318,298,180",   
               "time_stamp" : "2015-05-03"   
         }   
        ];   
        return movies;   
  }   

MovieService的完整代码片段如下所示:

import { Injectable } from '@angular/core';   
import { Movie} from './movie';   

@Injectable()   
export class MovieService {   
  getMovies(): Movie[] {   
    let movies: Movie[] = [   
          {   
               "movie_id" : 1,   
               "title" : "Iron   Man",   
               "phase" : "Phase   One: Avengers Assembled",   
               "category_name"   : "Action",   
               "release_year" :   2015,   
               "running_time" :   126,   
               "rating_name" : "PG-13",   
               "disc_format_name"   : "Blu-ray",   
               "number_discs" :   1,   
               "viewing_format_name"   : "Widescreen",   
               "aspect_ratio_name"   : " 2.35:1",   
               "status" : 1,   
               "release_date" :   "May 2, 2008",   
               "budget" : "140,000,000",   
               "gross" : "318,298,180",   
               "time_stamp" : "2015-05-03"   
         },   
          {   
               "movie_id" : 2,   
               "title" : "Spiderman",   
               "phase" : "Phase   One",   
               "category_name"   : "Action",   
               "release_year" :   2014,   
               "running_time" :   126,   
               "rating_name" : "PG-13",   
               "disc_format_name"   : "Blu-ray",   
               "number_discs" :   1,   
               "viewing_format_name"   : "Widescreen",   
               "aspect_ratio_name"   : " 2.35:1",   
               "status" : 1,   
               "release_date" :   "May 2, 2008",   
               "budget" : "140,000,000",   
               "gross" : "318,298,180",   
               "time_stamp" : "2015-05-03"   
         }   
        ];   
        return movies;   
  }   
}   

在这里,getMovies()函数返回类型为Movie[]的电影集合。

消费服务

我们已经准备好消费MovieService。让我们在一个组件中消费它。使用 Angular CLI,我们将通过执行以下命令来创建一个组件:

e:\Explore\packt\MovieDB>ng generate   component movie   
installing component   
  create src\app\movie\movie.component.css   
  create   src\app\movie\movie.component.html   
  create   src\app\movie\movie.component.spec.ts   
  create src\app\movie\movie.component.ts   

e:\Explore\packt\MovieDB>   

这将创建四个文件,分别是movie.component.tsmovie.component.htmlmovie.component.cssmovie.component.spec.tsmovie.component.ts文件的代码片段如下所示:

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

@Component({   
  selector: 'app-movie',   
  templateUrl: './movie.component.html',   
  styleUrls: ['./movie.component.css']   
})   
export class MovieComponent implements   OnInit {   

  constructor() { }   

  ngOnInit() {   
  }   

}   

MovieComponent@Component装饰器修饰,以及元数据,比如选择器、templateUrlstyleUrlsMovieService将被挂钩在ngOnInit方法下。让我们继续修改MovieComponent来消费MovieService

首先,我们需要将MovieService导入到我们的组件MovieComponent中。这个导入语句为MovieComponent提供了对MovieService的引用。但是要消费MovieService,我们需要创建MovieService的实例。我们该如何做呢?在标准方式中,我们可以实例化MovieService如下:

let movieService = new MovieService();   

OnInit生命周期钩子方法中导入MovieService并实例化MovieService后的MovieComponent的代码片段如下所示:

import { Component, OnInit } from '@angular/core';   
import { MovieService } from './movie.service';   
import { Movie } from './movie';   

@Component({   
  selector: 'app-movie',   
  templateUrl: './movie.component.html',   
  styleUrls: ['./movie.component.css']   
})   
export class MovieComponent implements   OnInit {   
  movies : Movie[];   

  constructor() { }   

  ngOnInit() {   
    let movieService = new   MovieService();   
    this.movies =   movieService.getMovies();   
  }   

}   

在这里,当OnInit事件被触发时,MovieService被实例化,并且通过调用getMovies()函数来检索电影集合。电影列表将被分配给MovieComponentmovies属性,以便在模板中进一步使用。

创建一个异步服务

我们刚刚创建了一个名为MovieService的服务,它同步调用getMovies()方法来检索电影集合。由于我们正在消费外部来源,比如 Web API,来检索电影集合,我们的应用程序必须等待服务器响应电影列表,因为getMovies函数是同步的。

因此,我们需要实现一种异步机制来检索电影集合。通过这种方式,我们可以避免使我们的应用程序等待 Web API 响应电影集合。我们可以通过使用 Promise 来实现这一点。

什么是 Promise?

Promise是一个真诚的保证,表示将执行某个操作。当服务器响应结果时,它会回调一个函数。我们请求一个异步服务,并使用回调函数执行某些操作,服务会用结果或错误调用我们的回调函数。您可以在第七章中了解更多关于 Promise 的内容,使用可观察对象进行异步编程

在服务中使用 Promise

让我们更新MovieService中的getMovies函数,以返回一个已解决的Promise,如下所示:

getMovies(): Promise<Movie[]> {   
    let movies: Movie[] = [   
          {   
               "movie_id" : 1,   
               "title" : "Iron   Man",   
               "phase" : "Phase   One: Avengers Assembled",   
               "category_name"   : "Action",   
               "release_year" :   2015,   
               "running_time" :   126,   
               "rating_name" : "PG-13",   
               "disc_format_name"   : "Blu-ray",   
               "number_discs" :   1,   
               "viewing_format_name"   : "Widescreen",   
               "aspect_ratio_name"   : " 2.35:1",   
               "status" : 1,   
               "release_date" :   "May 2, 2008",   
               "budget" : "140,000,000",   
               "gross" : "318,298,180",   
               "time_stamp" : "2015-05-03"   
         },   
          {   
               "movie_id" : 2,   
               "title" : "Spiderman",   
               "phase" : "Phase   One",   
               "category_name"   : "Action",   
               "release_year" :   2014,   
               "running_time" :   126,   
               "rating_name" : "PG-13",   
               "disc_format_name"   : "Blu-ray",   
               "number_discs" :   1,   
               "viewing_format_name"   : "Widescreen",   
               "aspect_ratio_name"   : " 2.35:1",   
               "status" : 1,   
               "release_date" :   "May 2, 2008",   
               "budget" : "140,000,000",   
               "gross" : "318,298,180",   
               "time_stamp" : "2015-05-03"   
         }   
        ];   
  return Promise.resolve(movies);   
}   

请注意,我们从getMovies函数中返回电影集合作为已解决的Promise。现在我们需要修改将电影集合分配给MovieComponent中的 movies 属性的代码。

MovieComponent中的现有代码将Promise分配给movies属性,而不是电影集合,因为MovieService中的getMovies现在返回已解决的Promise。因此,让我们修改ngOnInit事件的代码如下:

ngOnInit() {   
    let movieService = new   MovieService();   
    movieService.getMovies().then(movies   => this.movies = movies);   
}   

我们将我们的回调函数提供给Promisethen方法,所以getMovies中的链式函数then有命令将从 Web API 返回的电影集合分配给MovieComponent的属性this.movies

在这里,应用程序不会等待MovieService返回电影集合。movies属性从回调函数中获取分配的电影列表。

摘要

很酷!这就是本章的结束。我们了解了在应用程序中实现服务的重要性和优势。我们还学习了如何在组件中使用服务。

然而,直接实例化MovieService是一个不好的方法。组件不需要知道如何实例化服务;它们的唯一目的是知道如何使用服务。服务还使组件能够与MovieServices的类型和它们的实例化方式紧密耦合。这是不可接受的;组件应尽可能松散耦合。

在下一章中,我们将讨论使用依赖注入将服务注入到组件中,这样我们就可以拥有松散耦合的组件。

第十二章:应用依赖注入

在本章中,您将学习关于 Angular 依赖注入。依赖注入是 Angular 中最引人注目的特性之一;它允许我们创建可注入对象,可以在各种组件之间作为共享资源使用。

在本章中,我们将讨论以下内容:

  • 探索依赖注入

  • 详细了解提供者类

  • 了解分层依赖注入

  • 创建可注入对象

  • 学习将提供者注入到服务中

  • 学习将提供者注入到组件中

  • 学习为提供者类解析依赖项

  • 使用@InjectprovideuseValue装饰器创建示例

没有依赖注入的应用程序

如果没有依赖注入框架,开发人员的生活将非常艰难。看看不使用依赖注入的以下缺点:

  • 每次需要传递构造函数参数时,我们都需要编辑类的构造函数定义

  • 我们需要创建构造函数,并单独注入每个所需的依赖类

让我们看一个没有依赖注入的应用程序,以了解其中的挑战和不足之处:

class products {
 available;
 category;

 constructor() {
  this.available = new warehouse();
  this.category = new category();
 }
}

让我们分析前面的代码片段以更好地理解:

  1. 我们创建了一个名为productsclass

  2. constructor方法中,我们实例化了依赖类warehousecategory

  3. 请注意,如果warehousecategory类的构造函数定义发生更改,我们将需要手动更新所有类的实例。

由于作为开发人员,我们的任务是手动定义所有依赖项,因此前面的代码并不完全可测试和可维护。这就是 Angular 依赖注入的用武之地。

依赖注入 - 介绍

依赖注入DI)是一种编码模式,其中一个类接收依赖项而不是自己创建它们。一些开发人员和技术狂人也将其称为设计模式。

它被广泛使用,通常被称为 DI。我们将在所有章节中将依赖注入系统称为 DI。

以下是我们绝对需要 DI 的原因:

  • DI 是一种软件设计模式,其中一个类接收其依赖项而不是创建对象本身

  • DI 创建并提供动态所需的对象

  • 我们可以将可注入对象视为应用程序的可重用存储库

  • DI 允许远程开发团队独立开发依赖模块。

没有使用 DI,无法完全编写任何 Angular 应用程序。现在,让我们重新审视一下之前没有使用 DI 编写的代码,并使用 Angular DI 编写它:

class products {

constructor(private _warehouse: warehouse, private _category: category) {

  // use _warehouse and _category now as reference
 }
} 

在前面的代码中发生了什么:

  1. 我们创建了一个products类。

  2. constructor中,我们将依赖类--warehousecategory--作为参数传递。

  3. 我们现在可以在整个类中使用实例_warehouse_category

  4. 请注意,我们没有创建依赖类的对象;相反,我们只是通过 DI 系统接收它们。

  5. 我们不必担心warehousecategory所需的依赖关系;这将由 Angular DI 在内部解决。

现在我们知道了什么是 Angular DI,让我们专注于它是如何在我们的 Angular 应用程序中实现和使用的。在学习提供者类和更多内容之前,我们应该了解一些关于 Angular DI 框架的基本知识。

当然,我们将在接下来的几节中详细介绍这些。了解基本概念是很好的:

  1. @Injectable:这个装饰器标记一个类可供注入器实例化。

  2. @Inject:使用@Inject装饰器,我们可以将配置对象注入到任何需要它的构造函数中。

  3. Provider:提供者是我们注册需要注入的依赖项的方式。

现在让我们开始学习提供者类。

理解提供者类

要在我们的应用程序中开始使用 DI,我们需要了解提供者的概念。组件装饰器中的提供者配置告诉 Angular 需要提供哪些类给组件。

在提供者配置中,DI 接受一个类的数组,即我们要提供给组件的注入标记。我们还可以使用useClass指定要为注册的标记实例化的class

快速查看使用提供者配置的语法:

@Component({
 templateUrl: './calculate-tax.component.html',
 styleUrls: ['./calculate-tax.component.css'],
 providers: [MyTax]
})

在前面的代码中,我们告诉 Angular 前面的组件需要由MyTax类提供。

以下是使用提供者类的优点:

  • 提供者是每个注入器维护的

  • 每个provider提供一个 Injectable 的单个实例

  • 提供者类提供了调用方法的返回值

我们还可以明确提到应该从服务中使用的类。

这是一般的语法:

@Component({
 templateUrl: './calculate-tax.component.html',
 styleUrls: ['./calculate-tax.component.css'],
 providers: [
    { provide: MyTax, useClass: MyTax }
  ]
})

在前面的代码片段中,我们明确告诉 Angular 注入MyTax提供者并使用useClass配置使用MyTax类。

让我们更多地了解提供者类如何注册和使用;让我们看一下以下的图表:

让我们详细分析前面的图表,以了解关键要点:

  • 组件共享资源是通过提供者类提供的

  • 提供者类可以注册到多个组件中(一个或多个)

  • 我们还可以将提供者类注册到其他提供者类中

  • 在前面的图表中,组件#1依赖于提供者类#1

  • 在前面的图表中,组件#2依赖于提供者类#1提供者类#2

  • 在前面的图表中,组件#3依赖于提供者类#2提供者类#3

到目前为止,我们了解了 DI 对我们的应用程序有多么关键。DI 确实有助于组织数据,并且是实现独立模块或组件的最合适的设计模式。

这个想法是保持组件独立开发,并在提供者或可注入的地方编写更通用的共享或常用功能。

让我们快速创建一个提供者类的示例,它可以被注入到一个组件中。我们创建一个提供者类--MyTax.ts文件--并添加以下代码片段:

export class MyTax {
 public taxValue: string;
 constructor () {
     }

 getTaxes() {
  this.taxValue=Math.round(Math.random()*100);
  return this.taxValue; 
 }

}

让我们详细分析前面的代码片段:

  1. 我们创建了一个名为MyTax的提供者类。

  2. 我们将一个taxValue变量定义为数字。

  3. 我们创建了一个getTaxes方法,它将返回一个随机数。

  4. 我们给taxValue变量赋值,并通过getTaxes方法返回值。

现在,我们需要在我们组件的提供者数组配置中注册这个提供者类,并显示taxValue的值。

我们需要创建一个component类--calculate-tax.component.ts,并添加以下代码行:

import { Component } from '@angular/core';
import { MyTax } from './my-tax';

@Component({
 template: `<p>tax option: {{ taxName }}</p>`,
 styleUrls: ['./calculate-tax.component.css'],
 providers: [MyTax]
})
export class CalculateTaxComponent{

 public taxName: string;

 constructor( _myTax : MyTax) {
   this.taxName = _myTax.getTaxes();
 }

}

让我们详细分析前面的代码:

  1. 我们导入了最近创建的提供者类--MyTax

  2. 我们创建并定义了CalculateTax组件。

  3. 我们定义了一个taxName变量,并使用数据绑定在模板中映射了这个变量。

  4. 在构造函数中,我们在应用程序模块的提供者数组中注册了MyTax,Angular DI 将创建提供者类的实例并将其分配给_myTax

  5. 使用提供类的实例,我们调用了getTaxes方法。

运行应用程序,我们应该看到以下截图中显示的输出:

在本节中,您学习了如何创建提供程序类并在组件中注册它们以供使用。您可以将相同的提供程序类注册到多个组件中;在我们想要共享多个可重用方法的情况下,这无疑是理想的。

在下一节中,您将学习有关分层 DI 的知识--当我们有多个嵌套组件时。

理解分层 DI

在前面的部分中,我们介绍了通过提供程序类进行 DI,还介绍了在各个独立组件之间共享提供程序类。在本节中,您将学习如何在分层组件之间使用带有 DI 的提供程序类。

Angular 在内部创建了一个索引树,跟踪所有组件和正在创建的树结构,并维护其依赖矩阵,该矩阵在实时加载以提供所有必要的模块、服务和组件。

分层组件和各种组件之间的 DI 最好的部分是,我们不必担心这些依赖项是如何创建的,或者它们自身内部需要什么依赖项。

概述-分层组件和 DI

Angular 内部维护组件的树结构是一个公开的秘密。它还维护依赖项的树索引。

在任何真实的 Angular 应用程序中,我们将使用许多组件和服务。这些组件将具有从根组件到子组件和内部子组件等的树结构。

这在内部形成了一个组件树结构。由于我们的组件也将具有依赖项和可注入项,Angular 将在内部形成一个依赖项树矩阵,以跟踪和解析服务或组件所需的所有依赖项。

以下是您需要了解有关分层 DI 的关键事项:

  • Angular 框架在内部为组件创建了一个分层树结构的 DI

  • 提供程序类需要注册到组件中

  • 我们可以将提供程序类注册到其他提供程序类中

在下一节中,您将创建可注入的服务,并在组件中使用它们。

创建可注入项

我们不必创建 Angular 注入器,它是默认注入的。Angular 在引导过程中创建了一个应用程序范围的注入器。

我们使用@Injectable装饰器定义可注入的类,并在类中定义方法。@Injectable使得一个类可以被注入器实例化。

以下是创建@Injectable服务的示例代码:

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

@Injectable()
 export class ListService {
  getList() { 
   console.log("Demo Injectable Service");
  }
}

让我们详细分析代码片段:

  1. 我们从 Angular 核心模块中导入了Injectable

  2. 我们使用@Injectable装饰器告诉 Angular 以下类可以被注入,并且可以被注入器实例化。

  3. 我们创建了一个名为ListService的类。

  4. 我们实现了一个名为getList的方法,目前只是在console.log中打印一条消息。

注册提供者

注入器使用提供者创建依赖项。提供者需要在消费服务或组件中注册。通过注册它们,提供者类允许我们创建独立的可重用功能,可以由各个团队成员使用。

配置和注册提供者类还可以将功能分解为更小的模块,这样更容易维护和修改。我们可以以不同的方式将提供者类注册到服务和组件中。关于注入器,始终要牢记的重要点如下:

  • 我们必须在我们的NgModule、组件构造函数或指令中创建一个提供者

  • 在组件的构造函数中注册服务

我们在前面的部分创建了一个ListService服务,它有一个方法,现在可以被注册并在多个组件中使用:

让我们详细分析前面的图表,以了解我们正在构建的用例:

  1. 我们将创建一个@Injectable服务类ListService

  2. 我们将创建一个名为TestDiComponent的组件。

  3. 我们需要将ListService注册到TestDiComponent中。

那么,现在让我们立即开始学习如何在ngModule中注册提供者。看一下ngModule文件:

import { ListService } from "./shared/list.service";

@NgModule({
 providers: [
 {
  provide: ListService,
  useClass: ListService
 }
 ]
})

简而言之,上面的代码通常写成如下形式:

import { ListService } from "./shared/list.service";

@NgModule({
 providers: [
   ListService
 ]
})

让我们详细分析前面的代码片段:

  1. 我们已经将ListService服务类导入到ngModule中。

  2. 请注意,我们在提供者中注册了ListService。Angular 将在运行时内部解析并创建一个注入器。

  3. 在简写表示法中,我们只提到提供者的名称,Angular 将provide属性映射到useClass的值以进行注册和使用。

在前面的部分中,您学会了如何在ngModule的提供者配置数组中注册服务。

在 AppModule 中注册提供者与在组件中注册提供者的主要区别在于服务的可见性。在 AppModule 中注册的服务在整个应用程序中都可用,而在特定组件内注册的服务只在该组件内可用。

在组件内注册提供者

现在,您将学习如何在组件中注册提供者并在组件内使用可注入的服务类。

首先,让我们使用 Angular CLI 的ng命令快速生成一个组件和服务:

ng g component ./test-di

这将生成组件和所需的文件。命令的输出如下所示:

现在,我们必须在同一文件夹中生成一个 Angular 服务。

ng g service ./test-di

上述命令的输出如下:

我们看到 Angular CLI 生成了一个警告消息,指出服务已生成但未提供。

到目前为止,我们已经分别创建了组件和服务,但现在我们需要在组件中注册提供者,以便我们可以使用该服务。

在继续在组件中注册提供者之前,让我们快速查看一下 CLI 工具生成的服务代码。

这是我们的test-di.service.ts文件代码:

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

@Injectable()
 export class TestDiService {
  constructor() { }
}

这是由脚手架 Angular CLI 工具生成的默认代码。让我们添加一个我们想在组件内访问的方法:

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

@Injectable()

 export class TestDiService {
  getAuthors() {
  let Authors =[
   {name :"Sridhar"},
   {name: "Robin"},
   {name: "John"},
   {name: "Aditi"}
  ];
  return Authors;
 }
}

现在让我们在组件test-di.component.ts文件的 providers 数组中注册服务:

import { Component } from '@angular/core';
import { TestDiService } from './test-di.service';

@Component({
 selector: 'app-test-di',
 templateUrl: './test-di.component.html',
 styleUrls: ['./test-di.component.css'],
 providers: [TestDiService]
})

export class TestDiComponent{
 constructor(private _testDiService: TestDiService) {}
 authors = this._testDiService.getAuthors();
}

让我们详细分析上述代码:

  1. 我们创建了一个名为TestDiComponent的组件。

  2. 我们将新创建的服务TestDiService导入到组件中。

  3. 我们在 providers 中注册了TestDiService,告诉 Angular 动态创建服务的实例。

  4. Angular DI 将创建一个我们在constructor中传递的_testDiService服务类的新private实例。

  5. 我们使用了_testDiService服务的实例,并调用了getAuthors方法来获取作者列表。

运行应用程序,我们应该看到如下截图所示的输出:

到目前为止,您已经学会了创建一个Injectable服务,将服务注册到组件装饰器内的提供者数组中,并使用服务的实例来调用方法,这很棒。

在本节中,您学会了如何使用相同的一组共享提供者类创建多个组件。

带有依赖关系的提供者类

在前面的部分中,我们讨论了将服务注册到组件中,但是如果我们的服务本身需要一些依赖怎么办?在本节中,您将学习并实现解决服务所需依赖的方法。

为了更好地理解带有依赖关系的提供者类,让我们了解以下用例。我们有两个服务——CityServiceTestDiService,以及一个组件——TestDiComponent

让我们可视化这些服务和组件的依赖树:

让我们详细分析前面的图表,以了解我们正在构建的用例:

  1. 我们将创建一个Injectable服务——CityService

  2. 我们将创建一个Injectable服务——TestDiService

  3. 我们需要将CityService注册到TestDiService类中。

  4. 我们将创建一个TestDiComponent

  5. 我们需要将TestDiService注册到TestDiComponent中。

在本节中,我们将继续使用之前创建的服务TestDiService和组件TestDiComponent

现在,我们将创建一个名为CityService的额外服务,并将文件保存为city.service.ts

将以下代码片段添加到服务文件中:

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

@Injectable()
export class CityService {

 getCities() {
  let cities =[
  { name :"New York" },
  { name: "Dallas" },
  { name: "New Jersey" },
  { name: "Austin" }
  ];

 return cities;
 }
}

让我们分析前面的代码片段:

  1. 我们创建并导出了一个名为CityService的新服务。

  2. 我们实现了一个getCities方法,该方法返回一个城市列表。

创建服务后,我们导入文件并在app.module.ts文件中将服务注册为提供者:

import { CityService } from "./test-di/city.service";

@NgModule({
 providers: [
   CityService
 ]
})

由于我们在app.module.ts文件的 providers 数组中注册了该服务,它现在可以在整个应用程序中使用。

要在TestDiService中使用该服务,我们必须导入该服务并在构造函数中创建CityService的实例:

import { Injectable } from '@angular/core';
import { CityService } from './city.service';

@Injectable()
export class TestDiService {

  constructor(private _city: CityService) { }
    getAuthors() { 
      let Authors =[
         {name :"Sridhar"},
         {name: "Robin"},
         {name: "John"},
         {name: "Aditi"}
      ];
     return Authors;
  }
  getCityList() {
    let cities = this._city.getCities();
    return cities;
 }
}

在前面的部分提到的示例中,我们使用服务来显示作者列表。

现在,让我们分析前面的代码:

  1. 我们创建了一个名为CityService的服务,并在TestDiService中导入了该类。

  2. 我们在构造方法中创建了CityService类的一个实例——_City

  3. 我们定义了一个方法,即getAuthors

  4. 使用 this 运算符,我们在 getCityList 方法中调用了 CityService 类的 getCities 方法。

  5. getCities 方法返回城市列表。

运行应用程序,您将看到前面代码的输出,如下面的屏幕截图所示:

在本节中,您学习并实现了如何通过使用 @Injectable 装饰器注册其他提供者类来解决提供者类的依赖关系。

使用 @Inject、provide 和 useValue

让我们快速回顾一下学习 DI 的进展。我们讨论了如何编写提供者类和层次组件的依赖注入,以及如何使用 @injectable 装饰器编写可重用的提供者。

在本节中,您将学习如何使用 @InjectprovideuseValue 来在不同组件之间共享数据。

要声明一个服务可以在类中被注入,我们需要一个 @Injectable 装饰器。该装饰器告诉 Angular 将使用 @Injectable 定义的类可用于注入器,以便实例化到其他类、服务或组件中,并且该类应通过 DI 动态解析。我们主要用它们来编写通用服务并创建我们自己的存储库。

正如我们之前提到的,即使服务需要在其中注入依赖项,我们也使用 @Injectable 装饰器。我们还可以将服务注册到另一个服务或任何组件中。

每当我们需要注入构造函数参数的类型时,我们将使用 @inject 装饰器。

看一下 app.module.ts 文件中 ngModule 的以下示例代码:

import { ListService } from "./shared/list.service";

@NgModule({
 providers: [
  {
   provide: ListService,
   useClass: ListService
  }
 ]
})

关于前面的代码,有一些要注意的要点:

  1. 我们导入了之前创建的服务,即 ListService

  2. 现在我们已经导入了服务,我们需要将其添加到 providers 列表中。

  3. 我们明确说明需要注册服务名 ListService

  4. 使用 useClass,我们将指示 Angular 实例化并使用 ListService 类。

如果我们仔细注意,我们主要处理的是服务/提供者类。但是,如果我们需要注入某些变量,以便我们可以在不同的组件和服务之间共享值呢?

太棒了!这就是我们可以轻松使用 @Inject 装饰器并创建一个变量或类名,我们可以在其他组件和服务中重用。

现在看一下ngModule文件;我们已经修改它以适应我们想要在各种服务和组件之间共享的变量:

import { ListService } from "./shared/list.service";

@NgModule({
 providers: [
 {
   provide : 'username',
   useValue: 'Sridhar@gmail.com'
 }
 ]
})

让我们分析前面的代码:

  1. 在提供者中,我们创建了一个新的条目,对于provide,我们应用了一个名称username。请记住,无论您在这里提到的名称是什么,我们都需要在其他服务或组件中始终使用它。

  2. 我们为username变量提供了一个值。

  3. 请注意,这个值不会被更改或更新;把它想象成应用程序中的一个常量值。

现在我们已经创建了一个值常量提供者,让我们看看如何在我们的组件中使用它。

app.component.ts中,添加以下代码片段:

import { Component, Inject } from  '@angular/core';
 @Component({
 selector:  'app-root',
  templateUrl:  './app.component.html',
  styleUrls: ['./app.component.css']
 })  export  class  AppComponent {  title = 'Learning Angular - Packt Way';
  constructor ( @Inject('username') private  username ) {} } 

让我们详细分析前面的代码片段:

  1. 我们从@angular/core中导入了componentInject模块。

  2. 我们创建了我们的组件,并为组件的 HTML 和样式表定义了相应的 HTML 和 CSS 文件。

  3. AppComponent类中,我们定义了一个title变量并为其赋值。

  4. 我们创建了一个类的构造函数,并传递了一个@inject装饰器来传递我们在app.module.ts文件中定义的username名称。

  5. 现在我们已经在提供者数组配置中注册了username变量,我们可以在组件模板中的任何地方使用该变量的值。

太棒了,现在让我们运行这个应用程序;我们应该看到以下截图中显示的输出:

在以下截图中需要注意的一点是,以绿色标记的变量值'Sridhar@gmail.com'在模板中被打印出来。

在本节中,您学会了使用@Inject装饰器定义和使用常量提供者。

您学会了如何为我们的服务类使用@Injectable;我们可以将其注册为提供者,并在其他服务或组件中使用它。

我们可以定义一些常量变量,也可以注入和在不同组件中使用该值。

现在您应该能够创建多个可重用的服务、提供者类,以及常量变量,这些可以用来创建我们的应用程序存储库。

总结

在本章中,我们讨论了现在我们所知道的 Angular DI。DI 允许我们将提供者类和可注入对象注入到组件中使用提供者。我们学习并实现了提供者类和分层依赖注入。我们还学会了在NgModule中注册提供者,或者直接在组件内部注册提供者。

我们重点关注如何创建和配置注入器,以及如何在组件装饰器中注册服务提供者。

本章解释了提供者类也可以有依赖项,这些依赖项可以在内部再次注入到服务或组件中。在下一章中,您将学习关于 Angular 动画。Angular 动画是一个核心库,通过将动作和过渡应用到应用程序中,提供更好的用户体验。

我们将学习各种过渡和动作,以及如何设计动画;最重要的是,我们将在学习过程中创建一些很酷的东西。