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

10 天学会 Angular - 第 3 天

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (20投票s)

2017年8月9日

CPOL

15分钟阅读

viewsIcon

51390

downloadIcon

2575

在这篇文章中,我们将学习Templates, Events, Models, Directives 等。

如果你对这篇文章感兴趣,说明你已经了解如何

  • 安装 NodeJs 并获取 npm
  • 安装 TypeScript 编译器并使用它
  • 安装 lite-server 并使用它
  • 设置一个基本的 Angular 项目
  • 使用 Angular 创建一个简单的“Hello World”应用程序

  • 对 TypeScript 有基本的了解

今天,我们将通过更多的实验和演示,将我们的 Angular 技能提升到一个新的水平。让我们开始吧。

预先设置

在继续任何演示之前,我想让你做两件事。

  • 打开命令提示符,导航到“TypeScriptFiles”文件夹,然后键入“tsc -w”命令以在监视模式下启动 TypeScript 编译器。

    这将确保每次 TypeScript 文件更改时,它都会自动重新编译。每次更改后都不需要手动重新编译。

  • 打开另一个命令提示符,导航到“AngularProject”文件夹,然后键入“lite-server”命令来启动 Web 服务器。

    它将自动启动浏览器(Internet Explorer)。从地址栏复制 URL,打开 Chrome 并将相同的 URL 粘贴到地址栏。

    (注意:Chrome 在 JavaScript 调试方面是最好的,如果你愿意,也可以在自己喜欢的浏览器中进行测试。)

    向前看,任何时候“AngularProject”文件夹中的任何文件发生更改,浏览器都会自动刷新。自动刷新是“lite-server”的功能。

完整系列

  1. 第 1 天 – 第 1 部分
  2. 第 1 天 – 第 2 部分
  3. 第 2 天
  4. 第 3 天
  5. 第 4 天 – 第 1 部分
  6. 第4天 - 执行技巧
  7. 第 4 天 – 第 2 部分
  8. 第 4 天 – 第 3 部分
  9. 第 5 天(即将推出)
  10. 第 6 天(即将推出)
  11. 第 7 天(即将推出)
  12. 第 8 天(即将推出)
  13. 第 9 天(即将推出)
  14. 第 10 天(即将推出)

目录

Lab 2 – 模板 URL 介绍

在上一个演示中,我们创建了“AppComponent”。Angular 组件是两件事的组合。

  1. 一个 TypeScript 类,称为 AppComponent – 它被称为 ViewClassComponent 类。我们很快就会讨论它的实际用途。
  2. 模板 - 纯粹是 HTML,代表我们的 UI。

在当前的演示中,以上两件事都写在同一个 TypeScript 文件中。许多人(包括我)更喜欢将 HTML 写入独立的文件中,并实现清晰的关注点分离。

可以使用“templateUrl”来实现。让我们看看如何实现。

步骤 1 – 创建组件文件夹

在“AppModule”文件夹中创建一个名为“AppComponent”的新文件夹。(提醒一下,“AppModule”位于“TypeScriptFiles\AppModule”文件夹中。)

步骤 2 – 移动组件

将“app.component.ts”、“app.component.js”和“app.component.js.map”文件移到“AppComponent”文件夹中。

步骤 3 – 更正引用

按如下方式更改“AppModule”中的 import 语句

import {AppComponent} from "./AppComponent/app.component"

完整的 AppModule 代码如下所示

import {NgModule} from "@angular/core"
import {AppComponent} from "./AppComponent/app.component"
import {BrowserModule} from "@angular/platform-browser"
@NgModule({
    declarations:[AppComponent],
    bootstrap:[AppComponent],
    imports:[BrowserModule]
})
export class AppModule{
}

步骤 4 – 编译并测试输出

为了确保一切正常,让我们进行一次测试。转到浏览器。

您应该能够看到以下输出

步骤 5 – 创建模板文件

在“AppComponent”文件夹中创建一个名为“app.component.html”的新文件,并在其中放入以下内容

<h1>Welcome to Learn Angular in 10 days series</h1>
<i>Here is the Day 3</i>

步骤 6 – 将模板附加到组件

在附加到“AppComponent”类的“Component”装饰器中使用“templateUrl”选项而不是“template”选项,如下所示

import {Component} from "@angular/core"
@Component({
selector:'app-component',
templateUrl:'./app.component.html'
}
)
export class AppComponent{
}

步骤 7 - 编译并测试输出

转到浏览器。

检查错误。如您所见,在文件夹中搜索了‘app.component.html’。

步骤 8 – 更改模板 URL

templateURL”是相对的,但相对于“index.html”,而不是它的 TypeScript 文件。将“templateURL”更改为“./TypeScriptFiles/AppModule/AppComponent/app.component.html”,如下所示。完整代码如下

import {Component} from "@angular/core"
@Component({
selector:'app-component',
templateUrl:'./TypeScriptFiles/AppModule/AppComponent/app.component.html'
}
)
export class AppComponent{

}

步骤 9 - 编译并测试输出

转到浏览器。

Lab 3 – 使用相对 URL

在上一个实验中,我们只是将 HTML 从 TypeScript 文件中取出并放入一个独立的 HTML(模板)文件中。唯一的问题是“templateUrl”相对于“index.html”。

如果你想让“templateUrl”相对于相应的“.ts”文件,那么这个实验就是为你准备的。

这可以通过使用一个名为“systemjs-angular-loader.js”的 SystemJs 插件来实现。SystemJs 支持外部插件,我们可以用它们来扩展 SystemJs 的现有行为。“systemjs-angular-loader.js”动态地将“templateUrl”中的“component-relative”路径转换为绝对路径。

让我们进行演示。

步骤 1 – 下载插件

这个插件是由 Angular 团队创建的,它将包含在 Angular 团队提供的快速启动设置中。链接如下

从上面的链接下载 zip 文件并解压。文件“systemjs-angular-loader.js”位于“src”文件夹内。

(以后我们会有一个详细的演示来解释这个快速启动项目,现在我们手动完成所有事情,并理解所有内容的本质。)

步骤 2 – 包含插件

复制解压后的插件并粘贴到“AngularProject”文件夹中。现在打开“system.config.js”并添加插件支持,如下所示

...
packages: {
      'TypeScriptFiles': {
        defaultExtension: 'js',
        meta: {
          './*.js': {
            loader: 'systemjs-angular-loader.js'
          }
        }
      },
      rxjs: {//New line
        defaultExtension: 'js'
…

查看添加的新“meta”选项。

完整的“system.config.js

(function (global) {
  System.config({
    map: {
      '@angular/core': '/node_modules/@angular/core/bundles/core.umd.js',
      '@angular/common': '/node_modules/@angular/common/bundles/common.umd.js',
      '@angular/compiler': '/node_modules/@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 
      '/node_modules/@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 
      '/node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': '/node_modules/@angular/http/bundles/http.umd.js',
      '@angular/router': '/node_modules/@angular/router/bundles/router.umd.js',
      '@angular/forms': '/node_modules/@angular/forms/bundles/forms.umd.js',

      'rxjs':'/node_modules/rxjs',//New line
    },
     packages: {
      'TypeScriptFiles': {
        defaultExtension: 'js',
        meta: {
          './*.js': {
            loader: 'systemjs-angular-loader.js'
          }
        }
      },
      rxjs: {//New line
        defaultExtension: 'js'
      }
    }
  });
})(this);

步骤 3 – 更改组件

按如下方式将“templateUrl”值更改为相对路径

import {Component} from "@angular/core"
@Component({
selector:'app-component',
templateUrl:'./app.component.html'
}
)
export class AppComponent{

}

步骤 4 - 编译并测试输出

转到浏览器。

如您所见,演示现在也使用相对 URL 工作了。

注意

在 Angular 项目中使用 SystemJs 作为模块加载器不是强制性的。你也可以选择“requireJs”或其他。对于其他模块加载器,你需要选择之前的“Lab 2”方法来指定 URL,或者检查你的新模块加载器的文档以获得此类支持。

我建议你现在不要深入研究 SystemJs,而是专注于 Angular 学习。你以后可以用其他学习资源详细学习“SystemJs”。

在本系列的后续内容中,我们将有一个演示,届时我们将通过移除 SystemJs 并使用 WebPack 来完成工作,从而改进我们的项目。目前,请继续使用它。

Lab 4 – 插值

在这个演示中,我们将探索一个称为插值的 Angular 功能。

正如我之前所说,Angular 组件是两件事的组合。

  1. 模板 – (也称为视图)它代表组件的 UI 部分。
  2. 视图类/组件类 – 一个用“Component”装饰器装饰的自定义类。它封装了所需的 UI 数据和 UI 逻辑。

“插值是在组件模板中显示动态数据的一种方式”。它使用双大括号({{...}})编写。

让我们通过演示来理解。

步骤 1 – 初始化组件数据

打开‘app.component.ts’文件,在 AppComponent 类中创建两个变量,并在“Constructor”中简单地为它们赋一个虚拟值。

import {Component} from "@angular/core"
@Component({
selector:'app-component',
templateUrl:'./app.component.html'
}
)
export class AppComponent{
    CompnayUrl:string;
    CompanyName:string;
    constructor(){
        this.CompanyName="Train IT";
        this.CompnayUrl="http://JustCompile.com";
    }
}

步骤 2 – 显示组件数据

现在,按如下方式更改模板文件“app.component.html

<h1>Welcome to Learn Angular in 10 days series - Day 3</h1>
Company Name: <b>{{CompanyName}}</b>
<br>
Click <a href="{{CompanyUrl}}">here</a> to visit.

您可以看到插值在两个地方被使用。一个在粗体标签之间,第二个作为“href”值。

步骤 4 - 编译并测试输出

转到浏览器。

非常简单但实用的例子。

Lab 5 – 属性绑定

属性绑定是指将组件类中的变量绑定到模板中某个 HTML 元素的属性。这样的属性称为“输入属性”。

你是否感到困惑?让我们做一个非常快速的演示。

步骤 1 – 在 AppComponent 模板中实现属性绑定

按如下方式更改“app.component.html”中的超链接

<a [href]="CompanyUrl">here</a>

你注意到区别了吗?这次没有插值,而且“href”被包裹在“[”和“]”之间。

这称为“属性绑定”– AppComponent 类的“CompanyUrl”变量绑定到“a”元素的“href”属性,在这个例子中“href”被称为“输入属性”。

步骤 2 - 编译并测试输出

你将看到与之前完全相同的输出。这证明了插值和属性绑定做了同样的事情。但事实是,属性绑定不仅仅是显示值。让我们通过演示来理解。

步骤 3 – 更改组件类

AppComponent 类中添加一个名为“IsChecked”的新布尔类型变量。

...
export class AppComponent{
    CompnayUrl:string;
    CompanyName:string;
    IsChecked:boolean;
    constructor(){
        this.CompanyName="Train IT";
        this.CompnayUrl="http://www.JustCompile.com";
        this.IsChecked=true;
    }
}

步骤 4 - 更改模板

在模板中创建一个复选框控件,并将“IsChecked”属性与 HTML 元素的“checked”属性一起使用。

<h1>Welcome to Learn Angular in 10 days series - Day 3</h1>
Company Name: <b>{{CompanyName}}</b>
<br>
Is Active: <input type="checkbox" checked={{IsChecked}}>
<br>
Click <a href="{{CompnayUrl}}">here</a> to visit.

步骤 5 - 编译并测试输出

在浏览器中查看输出。

如您所见,Checkbox 被选中了。

步骤 6 – 更改 IsChecked

AppComponent 构造函数中,将“IsChecked”更改为 false

...
constructor(){
        this.CompanyName="Train IT";
        this.CompnayUrl="http://www.JustCompile.com";
        this.IsChecked=false;
 }
...

步骤 7 - 编译并测试输出

在浏览器中查看输出。

Checkbox 仍然被选中。

步骤 8 – 在模板中实现属性绑定

...
Is Active: <input type="checkbox" [checked]="IsChecked">
...

步骤 9 - 编译并测试输出

在浏览器中查看输出。

哇!:) Checkbox 现在被取消选中了。

步骤 10 – 更改 IsChecked

AppComponent 构造函数中,将“IsChecked”更改为 true

...
constructor(){
        this.CompanyName="Train IT";
        this.CompnayUrl="http://www.JustCompile.com";
        this.IsChecked=true;
 }
...

步骤 11 - 编译并测试输出

在浏览器中查看输出。

Checkbox 现在又回到了选中状态。

插值 vs 属性绑定

插值会调用变量的“toString”函数,然后设置值。所以值将始终是 string,而在属性绑定中,数据类型将被保留。

所以,简单来说 – 当变量是 string 类型时,插值是一个不错的选择,但对于其他数据类型,则首选属性绑定。

对于 string 值,插值更受欢迎,因为在插值中连接更容易。

请查看下面的示例

<p>{{FirstName}} – {{LastName}}</p>

它将简单地显示“FirstName”和“LastName”的连接,中间用连字符(-)分隔。

示例 – 当 FirstName 是“Sukesh”而 LastName 是“Marla”时,它将显示以下输出

Sukesh – Marla

Lab 6 – 在 Angular 中处理事件

在这个演示中,我们将看到事件绑定(也称为输出属性)的实际应用。

步骤 1 – 更改模板

在‘app.component.html’中添加一个简单的按钮。

<h1>Welcome to Learn Angular in 10 days series - Day 3</h1>
Company Name: <b>{{CompanyName}}</b>
<br>
Is Active: <input type="checkbox" [checked]="IsChecked">
<br>
Click <a href="{{CompnayUrl}}">here</a> to visit.
<br>
<input type="button" value="ChangeValue" (click)="UpdateValue()">

在上面的 HTML 中,“input”标签有一个特殊的属性称为“click”,它被写在“(”和“)”之间。这是“输出属性”。这是在 Angular 应用程序中处理事件的一种方式。

步骤 2 – 定义事件处理程序

打开‘app.component.ts’。在 AppComponent 类中,创建如下一个新的方法“UpdateValue

...
    IsChecked:boolean;
    constructor(){
        this.CompanyName="Train IT";
        this.CompnayUrl="http://www.JustCompile.com";
        this.IsChecked=false;
    }
    UpdateValue():void{
        this.CompanyName="Just Compile";
        this.CompnayUrl="http://www.sukesh-Marla.com";
        this.IsChecked=true;
    }
}

步骤 3 – 编译并测试输出

打开浏览器。您将看到以下输出。

当点击“ChangeValue”按钮时,组件类中的“UpadateValue”函数被调用。这称为事件绑定,并且“click”是示例中的输出属性。

按钮点击后,整个 UI 会使用新值重新渲染。这是因为 Angular 有一个称为更改检测的机制。更改检测是一种机制,它使 UI 与数据保持同步。每次数据更改时,UI 都会重新渲染。我们将在本系列的后续内容中更详细地讨论更改检测。

Lab 7 – 处理复杂对象

在下一个实验中,我们将了解如何在 Angular 中处理复杂对象。

步骤 1 – 创建模型

在编程世界中,Model 代表数据。让我们在我们的项目中也使用相同的术语。在“AppModule”文件夹中创建一个名为“Models”的新文件夹。

然后,在“Models”文件夹中创建一个名为“Employee.ts”的新 TypeScript 文件,如下所示

export class Employee{
    FName:string;
    LName:string;
    Salary:number;
}

步骤 2 – 在组件类中使用模型

现在是时候重新定义 AppComponent 类了。

删除 AppComponent 中的所有现有代码,然后创建一个类型为 Employee 的新全局变量,并用虚拟值初始化它。这是完整代码。

import {Component} from "@angular/core"
import {Employee} from "../Models/Employee"
@Component({
selector:'app-component',
templateUrl:'./app.component.html'
}
)
export class AppComponent{
    emp:Employee;

    constructor(){
        this.emp=new Employee();
        this.emp.FName="Sukesh";
        this.emp.LName="Marla";
        this.emp.Salary=10000;
    }
}

步骤 3 – 在模板中显示模型数据

最后,让我们重新定义模板文件“app.component.html”。这可以通过插值轻松完成。代码如下。

<h1>Welcome to Learn Angular in 10 days series - Day 3</h1>
Employee Name: {{emp.FName}} {{emp.LName}}
<br>
Salary: {{emp.Salary}}

步骤 3 – 编译并测试输出

打开浏览器。您将看到以下输出

简单的例子。

理解项目

正如我在第一天承诺的那样,我们将在一天结束时创建一个简单的 Angular 项目。现在是时候开始了。

我们将一步一步地创建项目。有时,我们会创建一些不符合最佳实践的东西。但没关系,我们会这样做,讨论其中的问题,然后采用最佳实践。

在学习方面,“我们应该学习一种技术的好坏实践”。

让我们开始理解我们要开发的内容。

上面的 UI 是 Employee UI。我们将为 CustomerProductCustomerSupplier 创建一些类似的 UI。

让我们一步一步地开始开发。

Lab 8 – 处理集合

让我们开始在单个 UI 中显示多个 Employees

为了做到这一点,我们必须使用一个非常有趣的 Angular 功能,那就是“属性指令”。

步骤 1 – 准备组件类

打开“app.component.ts”文件。删除 AppComponent 中的所有现有代码,然后创建一个类型为 Array<Employee> 的新全局变量,并用虚拟值初始化它。这是完整代码。

import {Component} from "@angular/core"
import {Employee} from "../Models/Employee"
@Component({
selector:'app-component',
templateUrl:'./app.component.html'
}
)
export class AppComponent{
    Employees:Array<Employee>;

    constructor(){
        this.Employees =new Array<Employee>();
        
        let e1=new Employee();;
        e1.FName="Sukesh";
        e1.LName="Marla";
        e1.Salary=10000;
        this.Employees.push(e1);

        let e2=new Employee();;
        e2.FName="Gabbar";
        e2.LName="Singh";
        e2.Salary=20000;
        this.Employees.push(e2);
        
        let e3=new Employee();;
        e3.FName="Dragon";
        e3.LName="Hunter";
        e3.Salary=30000;
        this.Employees.push(e3);
    }
}

步骤 2 – 重新定义模板

现在为了显示多个 Employees,我们必须使用 HTML“table”标签。挑战在于创建“tr”标签,因为它需要动态创建。对于列表中的每个 Employee,都应该创建一个“tr”。

作为最佳实践,Angular 项目中的所有 DOM 操作都必须在不直接访问组件类中的 DOM 的情况下进行。我们将在整个系列中探讨实现这一目标的各种方法。

对于我们当前的需求,我们将使用一个名为“*ngFor”的 Angular 指令。

“指令在附加时会改变 HTML 元素的默认行为。”

例如,“*ngFor”指令根据提供的表达式重复主机元素。

打开“app.component.html”,删除现有内容,并在此处放入以下内容。

<h1>Employee</h1>
<table border="1" >
    <tr>
        <th>Emp Name</th>
        <th>Salary </th>
    </tr>
    <tr *ngFor="let emp of Employees">
        <td>{{emp.FName}} {{emp.LName}}</td>
        <td>{{emp.Salary}}</td>
    </tr>
</table>

查看上面表格中的第二个“tr”。您会注意到它使用了*ngFor

让我们来分析这个例子中提供的表达式,它是let emp of Employees

Angular 迭代“Employees”(这是一个“Employee”数组),然后为列表中的每个项目创建一个行(“tr”)。在当前迭代中,“emp”将被设置为单个 Employee 项。

步骤 3 – 编译并测试输出

打开浏览器。您将看到以下输出

在本系列的后续内容中,我们将显示真实数据而不是硬编码的数据。

Lab 9 - 数据录入屏幕

在这个实验中,我们将重点放在我们项目的“数据录入”部分。

步骤 1 – 创建数据录入部分

打开‘app.component.html’并将以下 HTML 追加到其中。

<br>
<div>
    FName: <input type="text"><br>
    LName: <input type="text"><br>
    Salary: <input type="text"><br>
    <input type="button" value="Save" (click)="SaveEmployee()">
</div>

注意:请确保您已将上述 HTML 追加到现有 HTML 中。(不要替换它。)

步骤 2 – 定义 SaveEmployee

打开 app.component.ts 并按如下方式定义 SaveEmployee 方法

    SaveEmployee():void{
       let e1=new Employee();
        e1.FName="F1";
        e1.LName="L1";
        e1.Salary=40000;
        this.Employees.push(e1);
    }

步骤 3 – 编译并测试输出

打开浏览器。您将看到以下输出。

如您所见,我们只需向集合中添加一个新项,UI 就会自动重新渲染。这是因为“更改检测”。

我们有一个关于输入控件的非常详细的实验,届时我们将讨论将输入值获取到组件类的各种方法。目前的例子是插入硬编码的员工,我们就坚持这样做。

Lab 10 – 条件渲染

目前的 UI 同时显示 Employee 列表和数据录入屏幕,但根据我们的要求,一次只能显示一个。

在这个演示中,我们将在我们的项目中实现条件渲染。

让我们开始实现它。

规划逻辑

为了实现我们当前的需求(条件渲染),我们将执行以下步骤。

在组件类中,我们将进行以下更改

  • 创建一个布尔变量“IsAddNew”,它将表示 UI 中的当前条件。
  • 创建一个“ShowAddNew”函数,它将“IsAddNew”设置为 true
  • 创建一个“HideAddNew”函数,它将“IsAddNew”设置为 false
  • 在现有的“SaveEmployee”函数中,将“IsAddNew”设置为 false

在模板端,我们将使用 HTML 的“hidden”属性结合 Angular 中的属性绑定。

让我们实际操作并更好地理解它。

步骤 1 – 重新设计模板

让我们从重新设计 AppComponent 模板,即“app.component.html”开始。我们需要另外两个按钮,“Add New”和“Cancel”。

这是完整的 HTML。

<h1>Employee</h1>
<div [hidden]="IsAddNew">
    <input type="button" value="Add New" (click)="ShowAddNew()">
    <table border="1">
        <tr>
            <th>Emp Name</th>
            <th>Salary </th>
        </tr>
        <tr *ngFor="let emp of Employees">
            <td>{{emp.FName}} {{emp.LName}}</td>
            <td>{{emp.Salary}}</td>
        </tr>
    </table>
</div>
<div [hidden]="!IsAddNew">
    FName: <input type="text"><br>
    LName: <input type="text"><br>
    Salary: <input type="text"><br>
    <input type="button" value="Save" (click)="SaveEmployee()">
    <input type="button" value="Cancel" (click)="HideAddNew()">
</div>

您可以看到“IsAddNew”变量通过属性绑定到“hidden”属性。

不要错过第二个 div 中使用的“!”符号与“IsAddNew”。这意味着“hidden”将绑定到“IsAddNew”的相反值。换句话说,当“IsAddNew”不为 true 时,第二个“div”将隐藏/不可见。如果“IsAddNew”为 true,它将显示。

步骤 2 – 在组件类中实现逻辑

在“app.component.ts”文件中实现以下更改。

...
…
export class AppComponent{
    Employees:Array<Employee>;
    IsAddNew:boolean;
    constructor(){
        this.IsAddNew=false;
        this.Employees =new Array<Employee>();
        ...
        ...
    }
    SaveEmployee():void{
        ...
        ...
        this.IsAddNew=false;
    }
    ShowAddNew():void{
        this.IsAddNew=true;
    }
    HideAddNew():void{
        this.IsAddNew=false;
    }
}

步骤 3 – 编译并测试输出

打开浏览器。您将看到以下输出

总结

至此,我们完成了第 3 天的内容。

目前,我们的应用程序只是一个大的 UI。我们有一个组件,仅此而已。我们肯定没有遵循面向组件的风格。下次,我们将把我们的应用程序分解成多个小型、可重用、有意义且可组合的 Web 组件。

在第 4 天,我们还将学习如何创建自定义输入/输出属性以及如何在 Angular 中处理输入控件。在此之前,祝您编码愉快,敬请关注。

历史

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