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

学习 Angular 教程 - 第四部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017 年 9 月 22 日

CPOL

13分钟阅读

viewsIcon

28508

downloadIcon

561

进行 HTTP 调用并使用 Input 和 Outputs 创建自定义 Angular 组件

目录

其余文章的链接

  • 第一部分中,我们探讨了 Node、TypeScript、模块加载器/打包器和 VS Code。
  • 第二篇文章中,我们创建了一个简单的 Angular 应用程序,其中包含一个屏幕,并回顾了组件和模块等重要概念
  • 第三篇文章中,我们研究了如何实现 SPA 和验证。
  • 第四篇文章中,我们理解了如何进行 HTTP 调用以及如何使用 Input 和 Output 创建自定义 Angular 组件。
  • 第五部分中,我们涵盖了两个实验——一个是如何在 Angular 中使用 Jquery,另一个是延迟路由。
  • 第六部分中,我们再次涵盖了两个实验——管道和使用提供者的依赖注入。

Lab 9:进行 HTTP 调用

服务器端交互的重要性

如果没有服务器端交互,HTML 用户界面就毫无意义。通常在 Web 应用程序中,我们希望将最终用户输入的数据发送到服务器。在服务器端,我们会有某种服务,可以使用 Java、C# 等技术来创建。服务器端技术可以在数据库中保存、编辑或删除这些数据。

简单来说,我们需要了解如何从 Angular 代码向服务器端技术发出 HTTP 调用。

是的,这是一篇纯 Angular 文章。

我打算让这篇文章保持纯 Angular 的风格。所以像 ASP.NET MVC、Java 服务、PHP 这样的服务器端技术教学超出了范围。

因此,服务器端服务将使用“Angular inmemory Webapi”被“伪造”(Mocked)。请注意,这不是您将用于生产环境的服务。它仅用于测试和开发目的。

如果您想学习 ASP.NET MVC,可以从这个视频开始。

步骤 1:创建假的服务器端服务

尽管我们已经决定不使用 ASP.NET、Java 等创建专业的服务器端服务,但我们仍然需要一个。所以我们将使用一个名为“Angular in-memory web api”的假服务。

当我们进行 npm 安装时,“Angular inmemory web api”已经被安装。您可以在“package.json”文件中查看“Angular inmemory web api”的条目。

当我们进行 npm 安装时,“Angular inmemory web api”已经被安装。您可以在“package.json”文件中查看“angular inmemory web api”的条目。

所以让我们创建一个名为“Api”的文件夹,并在其中添加“CustomerApi.ts”文件,在该文件中,我们将编写代码来创建一个假的客户服务。

在这个假服务上,我们将进行 HTTP 调用。

下面是一个使用“angular-in-memory”开源项目创建的简单服务。在这个服务中,我们加载了一个简单的“customers”集合,其中包含一些示例客户记录。

import { InMemoryDbService } from 'angular-in-memory-web-api'
import {Customer} from "../Model/Customer"
export class CustomerApiService implements InMemoryDbService {
  createDb() {
    let customers =[
      { CustomerCode: '1001', CustomerName: 'Shiv' , CustomerAmount :100.23 },
      { CustomerCode: '1002', CustomerName: 'Shiv1' , CustomerAmount :1.23 },
      { CustomerCode: '1003', CustomerName: 'Shiv2' , CustomerAmount :10.23 },
      { CustomerCode: '1004', CustomerName: 'Shiv3' , CustomerAmount :700.23 }
    ]
    return {customers};
  }
}

所以现在当 Angular 进行 HTTP 调用时,它将命中这个内存中的 API。

因此,当您发出 HTTP GET 调用时,它将返回上述四个记录。当您发出 HTTP POST 调用时,它会将数据添加到内存中的集合中。

换句话说,我们不需要使用 ASP.NET 或 Java 服务创建专业的服务器端服务。

步骤 2:将 HTTP 和 WebAPI 模块导入到主模块

下一步是从“angular/http”导入“HttpModule”以及内存中的 API 到主模块。请记住,模块是组件的集合。所以“MainModule”包含“CustomerComponent”、“SupplierComponent”、“MasterpageComponent”和“WelcomeComponent”。

import { HttpModule} from '@angular/http';
import {CustomerApiService} from "../Api/CustomerApi"

同样在 imports 的“NgModule”属性中,我们需要指定“HttpModule”和“InMemoryWebApiModule”。

@NgModule({
    imports: [….
        HttpModule, 
        InMemoryWebApiModule.forRoot(CustomerApiService),
             …..],
    declarations: […],
    bootstrap: [….]
})
export class MainModuleLibrary { }

Shiv:谈谈 Httpmodule 和 Angular Webapi 的顺序。

HTTP 调用放在哪里?

下一个问题是,将 HTTP 调用放在什么地方是正确的?

所以,如果您看到 Angular 的正常架构,它是这样的:

  • 用户界面使用绑定“[()]”与 Angular 组件绑定。
  • 所以一旦最终用户开始在 UI 中输入数据,模型对象(在本例中是 customer 对象)将被加载并发送到 Customer 组件。
  • 现在 customer 组件拥有填充好的 customer 对象。所以将 HTTP 调用放在组件中的位置是正确的。

步骤 3:在 Customer 组件中导入 HTTP

所以,让我们继续在 Customer 组件中导入 Angular HTTP。

import { Http, Response, Headers, RequestOptions } from '@angular/http';

我们不需要使用 new 关键字创建 HTTP 对象,它将通过构造函数进行依赖注入。所以在 constructor 组件中,我们已经定义了 HTTP 的对象注入。

constructor(public http:Http){
    }

步骤 4:创建头部和请求信息

当我们将 HTTP 请求从客户端发送到服务器或接收响应时,头部信息会随请求和响应一起传递。在头部信息中,我们有内容类型、状态、用户代理等。

所以要创建一个请求,我们需要先创建一个头部,并使用该头部创建一个请求。我们需要提供的头部信息之一是我们发送到服务器的内容类型 - 是 XML、JSON 等。

下面是如何使用基本头部信息创建一个简单请求。

let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({ headers: headers });

步骤 5:进行 HTTP 调用和 Observable

Angular HTTP 使用一种称为 observable 的东西。所以 Angular 是一个观察者,它订阅 observable 就像 HTTP 响应一样。换句话说,它正在监听来自服务器的数据。

所以下面的代码表示我们将向“api/customers”URL 发出 GET 调用,当数据到来时,我们将成功的数据发送到“Success”函数,并在发生错误时,在“Error”函数中捕获它。

var observable = this.http.get("api/customers",  options);
        observable.subscribe(res => this.Success(res),
            res => this.Error(res));

下面是 ErrorSuccess 函数的代码。在“Success”函数中,我们将响应转换为 JSON 并将其赋值给组件中的“Customers”集合。如果出现错误,我们将在浏览器控制台中显示它。

Error(err) {
        console.debug(err.json());
    }
    Success(res) {
        this.Customers = res.json().data;
    }

步骤 6:创建一个简单的 POST 和 GET 调用

结合我们从步骤 4 和步骤 5 中收集到的所有知识,让我们写两个函数,一个用于显示数据,另一个用于发布数据。

下面是一个简单的“Display”函数,它发出一个 HTTP GET 调用。

Display(){   
        let headers = new Headers({
            'Content-Type': 'application/json'
        });
        let options = new RequestOptions({ headers: headers });
        var observable = this.http.get("api/customers",  options);
        observable.subscribe(res => this.Success(res),
            res => this.Error(res));
    }

一旦 customer UI 加载完毕,customercomponent 对象就会被创建。所以我们在 constructor 中调用了“Display”函数来加载 customers 集合。

export class CustomerComponent {
// other code removed for clarity
constructor(public http:Http){
            this.Display();
    }
// other codes removed for clarity
}

下面是一个简单的“Add”函数,它向服务器发出 POST 调用。在下面的 http POST 调用代码中,您可以看到 customer 对象作为第三个参数发送。在“Add”调用之后,我们调用了“Display”,以便我们可以看到新添加到服务器的数据。

Add(){
           let headers = new Headers({
            'Content-Type': 'application/json'
        });
        var cust:any = {};
        cust.CustomerCode = this.CurrentCustomer.CustomerCode;
        cust.CustomerName = this.CurrentCustomer.CustomerName;
        cust.CustomerAmount = this.CurrentCustomer.CustomerAmount;
        let options = new RequestOptions({ headers: headers });
        var observable = this.http.post("api/customers",cust,  options);
        observable.subscribe(res => this.Success1(res),
            res => this.Error(res));
        this.Display();
    }

在上面的“Add”函数中,您可以看到下面的代码创建了一个新的轻量级 customer 对象。那么为什么我们需要创建这个新的对象呢?

        var cust:any = {};
        cust.CustomerCode = this.CurrentCustomer.CustomerCode;
        cust.CustomerName = this.CurrentCustomer.CustomerName;
        cust.CustomerAmount = this.CurrentCustomer.CustomerAmount;

当前的 customer 对象包含很多其他东西,如验证、原型对象等。所以将整个对象发布到服务器没有意义。我们只想发送三个属性“CustomerCode”、“CustomerAmount”和“CustomerName”。

换句话说,我们需要创建一个轻量级的 DTO(Data Transfer Object),它只包含这些属性。

步骤 7:将组件连接到用户界面

现在我们的组件已经完成,我们需要使用 Angular 的“click”事件将“Add”函数绑定到按钮上。您可以看到 (click) 在圆括号内,换句话说,我们正在将某些东西(单击事件)从 UI 发送到对象。

<input type="button" 
value="Click" (click)="Add()" [disabled]="!(CurrentCustomer.formGroup.valid)"/>

另外,我们需要创建一个表,在该表中我们将使用“ngFor”循环并在 HTML UI 上显示 customers 集合。在下面的代码中,我们创建了一个临时对象“cust”,它遍历“Customers”集合,并在 tag 中,我们使用表达式({{cust.CustomerName}})来显示数据。

<table>
    <tr>
        <td>Name</td>
         <td>code</td>
          <td>amount</td>
    </tr>
      <tr *ngFor="let cust of Customers">
        <td>{{cust.CustomerName}}</td>
         <td>{{cust.CustomerCode}}</td>
          <td>{{cust.CustomerAmount}}</td>
    </tr>
</table>

继续运行应用程序。如果您添加“customer”对象,您应该会看到 HTTP 调用正在发生,并且列表正在 HTML 表中加载。

Lab 10:输入、输出和发射器

理论

可重用性是开发中最重要的方面之一。由于 Angular 是一种 UI 技术,我们希望创建 UI 控件并在不同的 UI 中重用它们。

例如,在 Customer UI 中,我们有表格网格显示。如果我们想在其他 UI 中创建网格,我们需要再次重复“<tr><td>”循环。如果我们能创建一个通用的可重用网格控件并将其插入到任何 UI 中,那就太好了。

如果我们想创建通用的可重用网格控件,您需要进行通用思考,您需要从输入和输出方面进行思考。

所以,如果您将此网格控件与 Customer UI 一起使用,您将获得一个 Customer 集合;如果您将它与 Supplier UI 一起使用,您将获得一个供应商集合。

为了实现这种通用思维过程,Angular 提供了三样东西:InputOutputEvent emitters。

Input 帮助我们定义用户控件的输入数据。Output 使用事件发射器将数据发送到包含用户控件的 UI。

规划组件的外观

首先,让我们规划一下我们的网格组件的外观。Grid 组件将在主 HTML 中使用“<grid-ui></grid-ui>” HTML 元素调用。Grid 组件将有三个属性:

  • grid-columns:这将是一个输入,我们将在其中指定网格的列名。
  • grid-data:这又将是一个输入,我们将提供要在网格上显示的数据。
  • grid-selected:这将是一个输出,通过发射器事件将选定的对象发送到包含的 UI。
<grid-ui  [grid-columns]=”In this we will give column names"
    [grid-data]="In this we will give data for grid"
    (grid-selected)="The selected object will be sent in event">
</grid-ui>

步骤 1:导入 Input、Output 和 Event Emitter

所以,首先,让我们为网格组件代码添加一个单独的文件,并将其命名为“GridComponent.ts”。

在此文件中,我们将编写 Grid 组件所需的所有代码。

第一步是添加必要的组件,这些组件将引入 InputOutput 功能。为此,我们需要从“angular/core”导入 component、InputOutputevent emitter 组件。

import {Component,
        Input,
        Output,
        EventEmitter} from "@angular/core"

步骤 2:创建可重用的 GridComponent 类

由于这是一个 component,我们需要使用“@Component”装饰器来装饰类。这个 component 的 UI 将在“Grid.html”中编写。您也可以在下面的代码中看到我们定义了选择器为“grid-ui”,您能猜到为什么吗?

如果您还记得我们在规划阶段,我们说过网格可以通过“<grid-ui>”标签调用。

@Component({
    selector: "grid-ui",
    templateUrl : "../UI/Grid.html"
})
export class GridComponent {
}

步骤 3:定义输入和输出

由于这是一个网格组件,我们需要网格的数据和列名。所以我们创建了两个数组集合:一个是“gridColumns”(包含列名),另一个是“gridData”(用于表格数据)。

export class GridComponent {
    // This will have columns
    gridColumns: Array<Object> = new Array<Object>();
    // This will have data
    gridData: Array<Object> = new Array<Object>();
}

有两个方法“setGridColumns”和“setGridDataSet”,它们将帮助我们为上面定义的两个变量设置列名和表格数据。

这些方法将使用“@Input”装饰器进行装饰,在其中,我们将放置在调用此组件时调用这些输入的名称。

// The top decorator and import code is removed
// for clarity purpose

export class GridComponent {
// code removed for clarity
    @Input("grid-columns")
    set setgridColumns(_gridColumns: Array<Object>) {
            this.gridColumns = _gridColumns;
        }
    @Input("grid-data")
    set setgridDataSet(_gridData: Array<Object>) {
            this.gridData = _gridData;
        }
}

输入装饰器中定义的名称将如下所示,在主 UI 中调用该组件时使用。

<grid-ui  [grid-columns]=”In this we will give column names"
    [grid-data]="In this we will give data for grid" >
</grid-ui>

步骤 4:定义 Event Emitters

正如在本实验的理论部分所讨论的,我们将有输入和输出。输出再次使用“@Output”装饰器定义,数据通过事件发射器对象发送。

要定义输出,我们需要使用“@Output”装饰器,如下面的代码所示。此装饰器定义在“EventEmitter”对象类型之上。您可以看到类型是“Object”,而不是“Customer”或“Supplier”,因为我们希望它是通用类型的。这样我们就可以将此输出与任何组件类型关联。

@Output("grid-selected")
    selected: EventEmitter<Object> = new EventEmitter<Object>();

现在,当任何最终用户从网格中选择一个对象时,我们需要使用“EventEmitter”对象通过调用“emit”方法来引发事件,如下所示。

// Other codes have been removed for clarity purpose.
export class GridComponent {
    @Output("grid-selected")
    selected: EventEmitter<Object> = new EventEmitter<Object>();

    Select(_selected: Object) {
        this.selected.emit(_selected);
    }
}

下面是到目前为止我们讨论过的“GridComponent.ts”的完整代码。

import {Component,
        Input,
        Output,
        EventEmitter} from "@angular/core"

@Component({
    selector: "grid-ui",
    templateUrl : "../UI/Grid.html"
})
export class GridComponent {
    gridColumns: Array<Object> = new Array<Object>();
    // inputs
    gridData: Array<Object> = new Array<Object>();

    @Output("grid-selected")
    selected: EventEmitter<Object> = new EventEmitter<Object>();
    @Input("grid-columns")
    set setgridColumns(_gridColumns: Array<Object>) {
            this.gridColumns = _gridColumns;
        }

    @Input("grid-data")
    set setgridDataSet(_gridData: Array<Object>) {
            this.gridData = _gridData;
        }
   
    Select(_selected: Object) {
        this.selected.emit(_selected);
    }
}

步骤 5:为可重用组件创建 UI

另外,我们需要为“GridComponent.ts”创建 UI。请记住,如果我们有一个 Angular 组件,我们就需要一个 HTML UI!

所以,在 UI 文件夹中,我们将添加“Grid.html”,其中我们将编写表格显示的代码。

在“GridComponent.ts”(请参阅本实验的步骤 4)中,我们定义了输入变量“gridColumns”,其中我们将提供网格的列。所以为此,我们使用“*ngFor”创建了一个循环,它将动态创建“<td>”列。

<table>   
 <tr>
        <td *ngFor="let col of gridColumns">
            {{col.colName}}
        </td>
    </tr>
</table>

为了在网格中显示数据,我们需要遍历“gridData”变量。

      
            {{colObj[col.colName]}}
        
        <a>Select</a>

因此,“Grid.html”的完整代码如下所示。

<table>
    <tr>
        <td *ngFor="let col of gridColumns">
            {{col.colName}}
        </td>
    </tr>
    <tr *ngFor="let colObj of gridData">
        <td *ngFor="let col of gridColumns">
            {{colObj[col.colName]}}
        </td>
        <td><a [routerLink]="['Customer/Add']" 
        (click)="Select(colObj)">Select</a></td>
    </tr>
</table>

步骤 6:在 Customer UI 中使用该组件

现在我们的可重用组件及其 UI 已经完成,让我们在“Customer.html” UI 中调用该组件。

下面是完整的正确代码,其中“grid-columns”定义了列名,在“grid-data”中,我们设置了“Customers”集合。这个“Customers”集合对象是从“CustomerComponent”类暴露出来的。这个“Customers”集合是我们之前在“HTTP”调用实验中创建的集合。该变量包含“Customer”数据的集合。

<grid-ui
      [grid-columns]="[{'colName':'CustomerCode'},
      {'colName':'CustomerName'},{'colName':'CustomerAmount'}]"
    [grid-data]="Customers"
    (grid-selected)="Select($event)"></grid-ui>

此外,我们需要确保删除旧的“<table>”代码,并用上面的“<grid-ui>”输入/输出标签替换它。

步骤 7:在主 Customer 组件中创建事件

如果您还记得在我们的“GridComponent”中,我们通过调用“emit”方法来发出事件。下面是它的代码片段。现在这个发出的事件需要在“CustomerComponent”中被捕获和处理。

export class GridComponent {
    Select(_selected: Object) {
        this.selected.emit(_selected);
    }
}

因此,为了在主组件中捕获该事件,我们需要在“CustomerComponent”文件中创建一个方法。所以,在 customer 组件的 typescript 文件中,我们将创建一个“Select”函数,该函数将从 GridComponent 接收选定的 customer,并将该选定的对象设置为“CurrentCustomer”对象。

export class CustomerComponent {
Select(_selected:Customer) {
        this.CurrentCustomer.CustomerAmount =_selected.CustomerAmount;
        this.CurrentCustomer.CustomerCode = _selected.CustomerCode;
        this.CurrentCustomer.CustomerName = _selected.CustomerName;
    }
}

步骤 8:在主模块中定义组件

另外,我们需要确保“GridComponent”已加载到主模块中。所以在主模块中,导入“GridComponent”并将其包含在“NgModule”装饰器的声明中,如以下代码所示。

import { GridComponent }   from '../Component/GridComponent';
@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes),
        InMemoryWebApiModule.forRoot(CustomerApiService),
             BrowserModule,ReactiveFormsModule,
             FormsModule,HttpModule],
    declarations: [CustomerComponent,
                MasterPageComponent,
                SupplierComponent,
                WelcomeComponent,
                GridComponent],
    bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }

就是这样!按 Control + B,运行服务器,看看您的可重用网格是否正常工作。

下一篇文章有什么内容?

第 5 部分,我们将研究如何进行动态路由以及如何在 Angular 中使用 Jquery。

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

历史

  • 2017 年 9 月 22 日:初始版本
© . All rights reserved.