65.9K
CodeProject 正在变化。 阅读更多。
Home

学习 Angular 教程 - 第六部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2017 年 11 月 8 日

CPOL

8分钟阅读

viewsIcon

19439

downloadIcon

331

Angular 中的 Pipes 和 Providers

目录

链接到其余文章

  • 第一部分中,我们研究了 Node、TypeScript、模块加载器/打包器和 VS Code。
  • 第二篇文章中,我们创建了一个简单的基础 Angular 应用程序,并回顾了组件和模块等重要概念。
  • 第三篇文章中,我们研究了 SPA 和验证的实现。
  • 第四篇文章中,我们了解了如何进行 HTTP 调用以及如何使用 Input 和 Output 创建自定义 Angular 组件。
  • 第五部分中,我们涵盖了两个实验,一个是如何在 Angular 中使用 JQuery 以及懒加载路由。
  • 第六部分中,我们再次涵盖了两个实验 - 管道和使用 providers 的 DI。

引言

在本文中,我们将介绍两个重要的实验:管道和 Providers。管道帮助我们创建可重用代码,通过这些代码我们可以格式化输出,而 Providers 则帮助我们为 Angular 创建良好的架构。

Lab 13: Angular 中的管道

这个实验是迄今为止所有实验中最小的一个,包含一些理论和三个步骤。所以如果你已经筋疲力尽了,这个实验就像一个休息的实验。

引言

管道接收数据输入并将其转换为不同的输出。例如,管道可以接受“Learn Angular”作为输入并将其转换为大写的“LEARN ANGULAR”,或者您可以输入“100”,您希望在 UI 上显示(转换为)“100.00 $”。

因此,对于这个实验,我们将进行以下管道转换:

  • Customer姓名转换为大写字母。
  • Customer金额转换为带有$货币符号的格式化金额。
  • 根据Amount,我们希望显示“Gold”或“Silver”客户状态。因此,如果金额超过100,它将显示“Gold Customer”;如果小于100,它将显示“Simple Customer”。

有关输出外观的更多详细信息,请参见图片。

理解管道的语法

在继续进行管道实验之前,让我们了解一下管道的语法。您需要在表达式“{{ }}”内编写管道语法。您需要先放入需要格式化的数据,然后是“|”符号,如下图所示。运行后,它会在 HTML 上显示最终格式化的输出。

管道语法写在 HTML 中。

步骤 1: 应用现成管道

管道有两种更广泛的类别:现成管道和自定义管道。您可以从https://angular.io/api?type=pipe获取现成管道的列表。

因此,要分别以大写形式显示“CustomerName”和带有货币符号的“CustomerAmount”,我们可以使用两个现成的过滤器:“uppercase”和“currency”。以下是相应的代码语法:

{{CurrentCustomer.CustomerName | uppercase}}
{{CurrentCustomer.CustomerAmount | currency}}

步骤 2: 为客户评级创建自定义管道

但是第三个要求有点复杂。在这种情况下,如果输入的金额大于100,我们需要显示“Gold Customer”,如果小于100,我们需要显示“Normal Customer”。

因此,让我们创建一个名为“Pipes”的单独文件夹,并在其中创建一个名为“GradePipes.ts”的文件,如下图所示:

在“GradePipes.ts”中,我们将编写具有逻辑的代码。所以第一件事是我们需要从核心导入“Pipe”和“PipeTransform”,因为这些类是管道的构建块。

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

接下来,我们需要创建一个实现“PipeTransform”接口的“GradePipes”类,并在其顶部使用“@Pipe”装饰器。每个pipe类都应该有一个“transform”方法,因为它是一个小的“t”“r”...“transform”,它有一个输入参数,即将在 UI 中提供的客户金额。

@Pipe({name: 'GradePipes'})
export class GradePipes implements PipeTransform {
  transform(value:number): string {
        if(value < 100){
            return "Simple Customer";
        }
        else{
            return "Gold Customer";
        }
  }
}

在“@Pipe”装饰器中,我们还指定了一个“name”。此名称将在 UI 中用于调用管道。

步骤 3: 将评级管道应用于 UI

最后,我们需要在 HTML UI 中调用该管道。现在我们可以像下面代码所示那样调用“GradePipes”。“CurrentCustomer.CustomerAmount”是输入,管道是“GradePipes”。

{{ CurrentCustomer.CustomerAmount|GradePipes}}

完成后,享受您的成果。

Lab 14: Providers、Services 和依赖注入

问题

鸣谢: https://medium.com/nestle-usa/4-tips-for-starting-your-baby-on-fruits-veggies-aafd6b2c0cde

良好的软件架构的一个标志是:

“当你在一个地方进行更改时,你不必在许多地方进行更改”。

现在考虑以下情况。在您的项目中,假设您有很多模块和 100 个(随便举个例子😊)组件。现在,在所有 100 个组件中,您都想实现日志记录实用程序。

在日志记录实用程序中也有不同的种类。有些记录器仅记录到浏览器的控制台,有些则使用对话框显示。

因此,假设这些记录器用于这 100 多个组件中以记录错误和调试消息。因此,在所有组件中,您将执行两项操作:

  • 使用“import”语法导入记录器组件。
  • 然后创建记录器的对象。

以下是代码片段:

import { ConsoleLogger} from "../Utility/Utility"
// code deleted for clarity
export class CustomerComponent {
    logger: ConsoleLogger = new ConsoleLogger ();
    // code removed for clarity
}

现在,假设将来有一天,您想通过对话框记录消息,请考虑您需要在 100 个组件中进行多少更改。

解决方案 DI 和 IOC

让我们总结一下导致所有组件都需要更改的原因。要解决这个问题,以下是解决方案,请缓慢而全神贯注地阅读以下句子。

“问题在于组件正在创建实用程序的对象,如果我们能将其反转,即如果我们能将‘实用程序’注入/提供给组件而不是由组件创建,这将使我们的架构更好”。

换句话说,我们需要实现“控制反转”。意味着将对象创建反转给其他人,而组件仅引用一个通用的实用程序引用。

因此,组件不再创建实用程序的对象,而是通过构造函数“请求”提供/注入“logger”对象。

IOC 是一个概念,要实现 IOC,我们需要使用 DI。我鼓励您观看此 YouTube 视频,其中详细解释了 DI 和 IOC。

现在我们已经了解了 DI 和 IOC 的概念,让我们来演示一下。

DI IOC 演示

这是一个简单的演示,我们将在我们的项目中实现它,以了解 Provider DI 的重要性。因此,我们将创建一个简单的父“Logger”类,并创建两种风格,一种用于显示消息,一种在对话框中显示,另一种显示在浏览器控制台中。

然后,我们将尝试弄清楚在一个地方进行更改并将其复制到所有地方有多么容易。

步骤 1: 创建带有 Injectable 属性的 Logger 实用程序类

因此,让我们在其中创建一个名为“Utility”的文件夹,并在其中创建这些记录器类。

让我们创建一个父“Logger”类,我们将从中继承并创建“Logger”类的不同风格。现在,因为这个类将被注入到组件中,我们需要用“@Injectable()”装饰器来标记它。“@Injectable()”装饰器在“angular/core”中的“Injectable”中可用。

import { Injectable } from '@angular/core';
@Injectable()
export class Logger{
   public  Log(){
    // some default logging
   }
}

以下是“ConsoleLogger”和“DialogLogger”类的代码,它们继承自“Logger”类。目前,这两个类都没有任何实际功能。此时,让我们专注于 DI 而不是日志记录功能。

@Injectable()
export class ConsoleLogger extends Logger{
    public Log(){
        // do console logging here
    }
}
@Injectable()
export class DialogLogger extends Logger{
    public Log(){
        // do console logging here
    }
}

步骤 2: 在模块中定义 Providers 和 Injection

正如我们之前讨论过的,目标是我们希望在一个地方进行更改,并且记录器类型应该在所有组件中发生变化。

现在,我们项目的整个加载(引导)是从“MainModuleLibrary”开始的,所以如果我们能在“MainModuleLibrary”级别进行注入,它将可用于所有下面的组件和模块。

在“MainModuleLibrary”中,使用“@NgModule”装饰器,我们需要提供将向下传递到所有组件的 providers。在 providers 中,我们需要指定两件事:

  • 首先,父类将在所有组件中被引用。因此,使用“provide”,我们指定了“Logger”类。
  • 其次,我们需要提供我们想要注入到所有组件中的子实现对象。这在“useClass”中提供。
providers: [
      {
        provide: Logger,
        useClass: ConsoleLogger
      }
    ]

包含“providers”的主模块的完整代码如下所示:

// Imports have been removed for clarity
import {Logger,DialogLogger,ConsoleLogger} from "../Utility/Utility"
@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes), 
            HttpModule, 
            InMemoryWebApiModule.forRoot(CustomerApiService),
             BrowserModule,
             FormsModule,
             ReactiveFormsModule],
    declarations: [
                    MasterPageComponent,
                WelcomeComponent],
    bootstrap: [MasterPageComponent],
    providers: [
      {
        provide: Logger,
        useClass: ConsoleLogger
      }
    ]
})
export class MainModuleLibrary { }

步骤 3: 定义用于注入的构造函数

既然我们已经在主模块上定义了将注入哪个对象。在组件中,我们需要在构造函数中公开“logger”类以用于 DI。

注意:您所有的组件都应该只引用“Logger”父类,而不是子类。您可以在下面的代码中看到,我们只导入了“Logger”类,并且仅通过构造函数引用了“logger”类。

// code removed for clarity
import {Logger} from "../Utility/Utility"
@Component({
    templateUrl: "../UI/Customer.html"
})
export class CustomerComponent {
    constructor(public http:Http , logger:Logger){
        this.Display();
    }
// code removed for clarity
}

步骤 4: 测试是否正常工作

运行应用程序,设置一个调试点,并查看注入的“ConsoleLogger”对象。如果将其更改为“DialogLogger”,您将看到相同的对象被注入。

深吸一口气,闭上眼睛思考。您正在主模块中进行更改,并且这些更改正在传播到所有地方。正如我们开始这个实验时所说,“好的架构就是在一个地方进行更改,并且这些更改会反映到所有地方”。

如需进一步阅读,请观看以下面试准备视频和分步视频系列。

历史

  • 2017 年 11 月 8 日:初始版本
© . All rights reserved.