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

学习 Angular 教程 - 第三部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2017 年 9 月 22 日

CPOL

16分钟阅读

viewsIcon

39199

downloadIcon

691

使用 Angular 路由和 Angular 验证实现 SPA。

目录

其余文章的链接

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

在本文中,我们将了解如何使用 Angular 路由机制创建 SPA。

实验 7:使用 Angular 路由实现 SPA

单页应用程序基础

如今,单页应用程序 (SPA) 已成为创建网站的风格。在 SPA 中,我们仅加载我们需要的内容,而不多加载其他任何内容。

右侧是一个简单的网站,顶部有 Logo 和标题,左侧有菜单链接,底部有页脚。

因此,当用户第一次访问网站时,主页的所有部分都将被加载。但是,当用户单击“供应商”链接时,仅加载“供应商”页面,而不会再次加载 Logo、标题和页脚。当用户单击“客户”链接时,仅加载“客户”页面,而不会加载所有其他部分。

Angular 路由可帮助我们实现相同的目标。

步骤 1:创建主页

由于一切都围绕主页展开,因此第一个合乎逻辑的步骤是创建“MasterPage”。

在此主页中,我们将为 Logo、标题、菜单、页脚、版权等创建占位符。这些部分仅在用户首次浏览网站时加载一次。之后,仅按需加载所需的页面。

以下是包含所有占位符部分的代码示例。您也可以在此代码中看到,我们保留了一个“DIV”标签部分,我们将在此部分按需加载页面。

以下是“MasterPage”的整体主要部分。请注意“dynamicscreen”名称的“DIV”部分,我们打算在此处动态加载屏幕。稍后我们将填充这些部分。

<table border="1" width="448">
<tr>
<td>Logo</td>
<td width="257">Header</td>
</tr>
<tr>
<td>Left Menu</td>
<td width="257">
<div id=”dynamicscreen”>
  Dynamic screen will be loaded here
</div>
</td>
</tr>
<tr>
<td>Footer</td>
<td width="257">Copyright</td>
</tr>
</table>

步骤 2:创建供应商页面和欢迎页面

让我们再创建两个 HTML UI,一个“Supplier”页面和一个“Welcome”页面。在这两个 HTML 页面中,我们没有做太多事情,只有问候语。

以下是供应商页面的文本。

这是供应商页面

以下是欢迎页面的文本。

欢迎访问本网站

步骤 3:重命名 Index.html 中的占位符

正如在第 1 部分中所解释的,“Index.html”是启动页面,它使用 systemjs 引导所有其他页面。在前一课中,“Index.html”内部加载了“Customer.html”页面。但现在我们有了主页,所以 index 页面内部会加载“MasterPage.html”。

为了使其更有意义,让我们将“customer-ui”标签重命名为“main-ui”。在此“main-ui”部分中,我们将加载主页,当最终用户单击主页左侧菜单链接时,将加载供应商、客户和欢迎页面。

所以,如果观察流程,首先加载 index.html,然后在“main-ui”内部加载“masterpage.html”。

步骤 4:从 CustomerComponent 中移除选择器

现在,index.html 中首先加载的页面将是 Masterpage 而不是 Customer 页面。因此,我们需要从“CustomerComponent.ts”中移除选择器。此选择器将在后面的部分移至主页组件。

CustomerComponent.ts”的最终代码如下所示。

import {Component} from "@angular/core"
//importing the customer model
import {Customer} from '../Model/Customer'
@Component({
    templateUrl: "../UI/Customer.html"
})
export class CustomerComponent {
    //this is binding
    CurrentCustomer:Customer = new Customer();
}

步骤 5:为 Master、Supplier 和 Welcome 页面创建组件

每个启用 Angular 的 UI 都应该有一个组件代码文件。我们已经创建了三个用户界面,因此我们需要三个组件代码文件。

在组件文件夹中,我们将创建三个组件 TS 文件“MasterPageComponent.ts”、“SupplierComponent.ts”和“WelcomeComponent.ts”。

您可以将组件代码文件可视化为 Angular UI 的代码后。

首先,让我们从“MasterPage.html”组件开始,我们将其命名为“MasterPageComponent.ts”。此主页将在初始引导过程中加载到“Index.html”中。您可以看到在此组件中,我们放置了选择器,这将是唯一具有选择器的组件。

import {Component} from "@angular/core"

@Component({
    selector: "main-ui",
    templateUrl: "../UI/MasterPage.html"
})
export class MasterPageComponent {
}

以下是“Supplier.html”的组件代码。

import {Component} from "@angular/core"

@Component({
  
    templateUrl: "../UI/Supplier.html"
})
export class SupplierComponent {
}

以下是“Welcome.html”的组件代码。SupplierWelcome 组件都没有选择器,只有主页组件有,因为它将是加载到 index 页面的启动 UI。

import {Component} from "@angular/core"

@Component({
    templateUrl: "../UI/Welcome.html"
})
export class WelcomeComponent {
}

步骤 6:创建路由常量集合

一旦主页在 index 页面中加载,最终用户将单击主页链接浏览到供应商页面、客户页面等。现在,为了让用户能够正确浏览,我们需要定义导航路径。这些路径将在后续步骤的“href”标签中指定。

当浏览这些路径时,它将调用组件,组件将加载 UI。下表包含三列。第一列指定路径模式,第二列指定浏览这些路径时将调用的组件,最后一列指定将加载的 UI。

路径/URL 组件 (Component) 将加载的 UI
/ WelcomeComponent.ts Welcome.html
/Customer CustomerComponent.ts Customer.html
/Supplier SupplierComponent.ts Supplier.html

路径和组件条目需要定义在一个简单的字面量集合中,如下面的代码所示。您可以看到“ApplicationRoutes”是一个简单的集合,我们在其中定义了路径和将被调用的组件。这些条目是根据顶部指定的表制作的。

import {Component} from '@angular/core';
import {CustomerComponent} from '../Component/CustomerComponent';
import {SupplierComponent} from "../Component/SupplierComponent";
import {WelcomeComponent} from "../Component/WelcomeComponent";

export const ApplicationRoutes = [
    { path: 'Customer', component: CustomerComponent },
    { path: 'Supplier', component: SupplierComponent },
     { path: '', component:WelcomeComponent  }
];

作为最佳实践,我们将所有上述代码定义在一个单独的“routing”文件夹和“routing.ts”文件中。

步骤 7:定义 routerLink 和 router-outlet

在“第 6 步”的集合中定义的导航(路由)需要在我们尝试在网站内部导航时引用。例如,在主页中,我们定义了左侧菜单超链接。

所以,我们不需要使用 HTML 的“href”标签,而是需要使用“[routerLink]”。

<a href=”Supplier.html”>Supplier</a>

我们需要使用“[routerLink]”,而“[routerLink]”的值将是上一步定义的路由集合中指定的路径。例如,在“ApplicationRoutes”集合中,我们为 Supplier 路径做了一个条目,我们需要在锚定标签中指定路径,如下面的代码所示。

<a [routerLink]="['Supplier']">Supplier</a>

当最终用户单击主页左侧的链接时,页面(供应商页面、客户页面和欢迎页面)将加载到“div”标签内。为此,我们需要定义“router-outlet”占位符。在此占位符内,页面将动态加载和卸载。

<div id=”dynamicscreen”>
<router-outlet></router-outlet>
</div>

因此,如果我们用“router-link”和“router-outlet”更新“第 1 步”中定义的主页,我们将得到如下代码。

<table border="1">
<tr>
<td><img src="http://www.questpond.com/img/logo.jpg" alt="Alternate Text" />
</td>
<td>Header</td></tr><tr>
<td>Left Menu<br /><br /><br />
<a [routerLink]="['Supplier']">Supplier</a> <br /><br />
<a [routerLink]="['Customer']">Customer</a></td><td>
<div id=”dynamicscreen”>
<router-outlet></router-outlet>
</div>
</td>
</tr>
<tr>
<td>Footer</td><td></td>
</tr>
</table>

步骤 8:在主模块中加载路由

为了启用“ApplicationRoutes”中定义的路由集合路径,我们需要将其加载到“MainModuleLibrary”中,如下面的代码所示。“RouterModule.forRoot”有助于在模块级别加载应用程序路由。

一旦在模块级别加载,它将可用于该模块内加载的所有组件进行导航。

@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes),
             BrowserModule,
             FormsModule],
    declarations: [CustomerComponent,MasterPageComponent,SupplierComponent],
    bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }

包含路由的完整代码如下所示。

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {FormsModule} from "@angular/forms"
import { CustomerComponent }   from '../Component/CustomerComponent';
import { SupplierComponent }   from '../Component/SupplierComponent';
import { MasterPageComponent }   from '../Component/MasterPageComponent';
import { RouterModule }   from '@angular/router';
import { ApplicationRoutes }   from '../Routing/Routing';  

@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes),
             BrowserModule,
             FormsModule],
    declarations: [CustomerComponent,MasterPageComponent,SupplierComponent],
    bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }

步骤 9:定义 APP BASE HREF

Router 模块需要一个根路径。换句话说,“Supplier”路由将变为“companyname/Supplier”,“Customer”路由将变为“companyname/Customer”,依此类推。如果您不提供路由,您将遇到如下错误。

因此,在您的“Index.html”中,我们需要添加 HTML BASE HREF 标签,如下面的突出显示代码所示。此时,我们不提供任何目录。

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
</head>
<base href="./">
<!—Other code has been removed for clarity -->

步骤 10:查看输出

现在运行网站,尝试浏览到 UI 文件夹,您应该会看到下面的动画视频输出。您可以看到 Logo 只加载了一次,之后当用户单击供应商链接时,客户链接的图像不会重复加载。

步骤 11:修复“无法匹配任何路由”错误

如果您按“F12”并在 Chrome 浏览器的控制台部分查看,您会看到如下错误。您能猜出错误是什么吗?

您当前的 Angular 应用程序已启用路由。因此,浏览的每个 URL 都会在路由集合中查找。
因此,您浏览的第一个 URL 是“/UI”,它会尝试在您的路由集合中查找,但找不到。

所以这就是为什么会抛出上述错误。

为了解决这个问题,为“UI”路径再添加一个条目,并将其指向“WelcomeComponent”。

export const ApplicationRoutes = [
    { path: 'Customer', component: CustomerComponent },
    { path: 'Supplier', component: SupplierComponent },
     { path: '', component:WelcomeComponent  },
    { path: 'UI', component:WelcomeComponent  }

];

理解流程

  1. 最终用户加载 index.html 页面。
  2. Index.html 触发 systemjs 并加载 masterpage.html
  3. 当最终用户单击主页链接时,其他页面会在“router-outlet”占位符中加载。

实验 8:使用 Angular 表单实现验证

在此实验中,我们将尝试了解如何使用 Angular 框架实现验证。

良好验证结构的要求

验证是软件应用程序的重要组成部分。

验证通常应用于用户界面 (Forms),用户界面包含控件,我们在控件上应用验证。

在 Angular 表单验证中,架构结构如下。

顶部是 FormGroupFormGroup 包含 FormControlsFormControls 包含一个或多个验证。

实现 Angular 验证有三个主要步骤:

  1. 创建 FormGroup
  2. 创建 FormControl 并添加适当的验证。
  3. 将这些验证映射到 HTML 表单。

在此实验中,我们将实现哪种验证?

在此实验中,我们将在我们的 Customer 屏幕上实现以下验证:

  • 客户名称必须填写。
  • 客户代码必须填写。
  • 客户代码的格式应为 A1001、B4004 等。换句话说,第一个字符应为大写字母,后跟 4 位数字。

注意:客户代码具有复合验证。

验证放在哪里?

在开始验证之前,我们需要决定将验证放在哪个合适的位置。如果您在 Angular 中看到,我们有三个主要部分 - UI、Component 和 Model。所以让我们思考一下将验证放在哪个层是合适的。

  • UI:UI 主要关注控件的外观、感觉和定位。因此,在此层放置验证是个坏主意。是的,在此层,我们将应用验证,但验证代码应位于其他层。
  • Component:Component 是绑定 UI 和 Model 的代码后 (Binding code)。在此层,应放置更多与绑定相关的活动。一个组件可以使用多个模型,因此如果我们在这里放置验证逻辑,则需要在其他组件中也进行重复。
  • Model:Model 代表现实世界的实体,如人、用户、管理员、产品等。行为(即验证)是模型的一部分。模型具有数据和行为。因此,验证应该存在的正确位置是此层。

因此,让我们将验证作为模型的一部分。

步骤 1:导入 Angular 验证器所需的组件

所以第一步是在客户模型中导入 Angular 验证器所需的组件。所有 Angular 验证器组件都位于“@angular/forms”文件夹中。我们需要导入五个组件:NgFormFormGroupFormControlValidators

  • NgForm:Angular 验证标签
  • FormGroup:帮助我们创建验证集合
  • FormControlValidators:帮助我们在 FormGroup 中创建单个验证
  • FormBuilder:帮助我们创建 Form group 和 Form controls 的结构。请注意,一个 Form group 可以包含多个 FormControls
import {NgForm,
    FormGroup,
    FormControl,
    Validators,
    FormBuilder } from '@angular/forms'

步骤 2:使用 FormBuilder 创建 FormGroup

第一步是创建一个 FormGroup 对象,我们将在其中包含验证集合。FormGroup 对象将使用“FormBuilder”类构造。

formGroup: FormGroup = null; // Create object of FormGroup
var _builder = new FormBuilder();
this.formGroup = _builder.group({}); // Use the builder to create object

步骤 3:添加简单验证

创建 FormGroup 对象后,下一步是向 FormGroup 集合添加控件。要添加控件,我们需要使用“addControl”方法。 “addControl”方法中的第一个参数是验证的名称,第二个参数是要添加的 Angular 验证器类型。

以下是一个简单代码,我们在其中使用“Validators.requiredFormControl 添加一个“CustomerNameControl”。请注意,“CustomerNameControl”不是保留关键字。它可以是任何名称,如“CustControl”。

this.formGroup.addControl('CustomerNameControl', new
            FormControl('',Validators.required));

步骤 4:添加复合验证

如果要创建复合验证,则需要创建一个集合并使用“compose”方法添加它,如下面的代码所示。

var validationcollection = [];
validationcollection.push(Validators.required);
validationcollection.push(Validators.pattern("^[A-Z]{1,1}[0-9]{4,4}$"));
this.formGroup.addControl('CustomerCodeControl', 
     new FormControl('', Validators.compose(validationcollection)));

包含验证的完整模型代码

以下是包含前面部分讨论的所有三个验证的完整 Customer 模型代码。我们还注释了代码,以便您可以理解。

// import components from angular/form
import {NgForm,
    FormGroup,
    FormControl,
    Validators,
    FormBuilder } from '@angular/forms'
export class Customer {
    CustomerName: string = "";
    CustomerCode: string = "";
    CustomerAmount: number = 0;
    // create object of form group
    formGroup: FormGroup = null;
   
    constructor(){
        // use the builder to create the
        // the form object
        var _builder = new FormBuilder();
        this.formGroup = _builder.group({});

        // Adding a simple validation
        this.formGroup.addControl('CustomerNameControl', new
            FormControl('',Validators.required));
       
        // Adding a composite validation
        var validationcollection = [];
        validationcollection.push(Validators.required);
        validationcollection.push(Validators.pattern("^[A-Z]{1,1}[0-9]{4,4}$"));
        this.formGroup.addControl('CustomerCodeControl', new
            FormControl('', Validators.compose(validationcollection)));
    }  
}

步骤 5:在 CustomerModule 中引用“ReactiveFormsModule”

// code has been removed for clarity.
import { FormsModule, ReactiveFormsModule } from "@angular/forms"
@NgModule({
    imports: [RouterModule.forChild(CustomerRoute),
                CommonModule,
                FormsModule,
                ReactiveFormsModule,
                HttpModule,
                InMemoryWebApiModule.forRoot(CustomerService)],
    declarations: [CustomerComponent, GridComponent],
    bootstrap: [CustomerComponent]
})
export class CustomerModule {
}

步骤 6:将 formGroup 应用于 HTML 表单

下一件事是将“formGroup”对象应用于 HTML 表单。为此,我们需要在“[formGroup]” Angular 标签中使用,我们需要在其中指定通过 customer 对象公开的“formGroup”对象。

<form [formGroup]="CurrentCustomer.formGroup">
</form>

步骤 7:将验证应用于 HTML 控件

下一步是将 formgroup 验证应用于 HTML 输入控件。这通过使用“formControlName” Angular 属性完成。在“formControlName”中,我们需要提供在创建验证时创建的表单控件名称。

<input type="text" formControlName="CustomerNameControl"
[(ngModel)]="CurrentCustomer.CustomerName"><br /><br />

步骤 8:检查验证是否正常

当用户开始填写数据并满足验证时,我们希望检查所有验证是否正常,并相应地显示错误消息或启用/禁用 UI 控件。

为了检查所有验证是否正常,我们需要使用“formGroup”的“valid”属性。以下是一个简单示例,其中按钮的禁用状态取决于验证是否有效。“[disabled]”是一个 Angular 属性,用于启用和禁用 HTML 控件。

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

步骤 9:检查单个验证

CurrentCustomer.formGroup.valid”会检查“FormGroup”的所有验证,但如果我们想检查控件的单个验证怎么办?

为此,我们需要使用“hasError”函数。
CurrentCustomer.formGroup.controls['CustomerNameControl'].hasError('required')”会检查“CustomerNameControl”是否满足“required”验证规则。以下是一个简单代码,我们在其中显示错误消息在“div”标签中,该标签根据“hasError”函数返回 truefalse 来显示或隐藏。

还要注意“hasError”前面的“!”(非),这意味着如果“hasError”为 true,则 hidden 应为 false,反之亦然。

<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerNameControl'].hasError
('required'))">Customer name is required </div>

步骤 10:独立元素

在我们的表单中,我们有三个文本框:“CustomerName”、“CustomerCode”和“CustomerAmount”。在这三个文本框中,只有“CustomerName”和“CustomerCode”有验证,而“CustomerAmount”没有验证。

这有点奇怪,但如果我们不为位于具有“formGroup”指定的“form”标签内的用户控件指定验证,您将遇到一个长异常,如下所示。

Error: Uncaught (in promise): Error: Error in ../UI/Customer.html:15:0 caused by: 
      ngModel cannot be used to register form controls with a 
      parent formGroup directive. Try using
      formGroup's partner directive "formControlName" instead.  Example:

    <div [formGroup]="myGroup">
      <input formControlName="firstName">
    </div>

    In your class:

    this.myGroup = new FormGroup({
       firstName: new FormControl()
    });

      Or, if you'd like to avoid registering this form control, 
      indicate that it's standalone in ngModelOptions:

      Example:
      
    <div [formGroup]="myGroup">
       <input formControlName="firstName">
       <input [(ngModel)]="showMoreControls" [ngModelOptions]="{standalone: true}">
    </div>
  
Error: 
      ngModel cannot be used to register form controls with a parent formGroup directive. 
      Try using
      formGroup's partner directive "formControlName" instead.  Example:

      
    <div [formGroup]="myGroup">
      <input formControlName="firstName">
    </div>

    In your class:

    this.myGroup = new FormGroup({
       firstName: new FormControl()
    });

      Or, if you'd like to avoid registering this form control, 
      indicate that it's standalone in ngModelOptions:

      Example:

      
    <div [formGroup]="myGroup">
       <input formControlName="firstName">
       <input [(ngModel)]="showMoreControls" [ngModelOptions]="{standalone: true}">
    </div>

上述错误可以简化为三点:

  1. 它表明您将一个 HTML 控件包含在一个具有 Angular 表单验证的 HTML FORM 标签内。
  2. HTML FORM 标签内的所有应用了 Angular 验证的控件都应该有验证。
  3. 如果 Angular 表单验证中的 HTML 控件没有验证,您可以采取以下任一措施来消除异常:
    • 您需要将其指定为standalone控件。
    • 将控件移出 HTML FORM 标签。

以下是如何为 Angular 验证指定“standalone”的代码。

<input type="text" [ngModelOptions]="{standalone:true}"
[(ngModel)]="CurrentCustomer.CustomerAmount"><br /><br />

还可以讨论我们可以从表单控件中移除什么以及会发生什么。

应用了验证的 Customer UI 的完整代码

以下是完整的 Customer UI,已将所有三个验证应用于“CustomerName”和“CustomerCode”控件。

<form [formGroup]="CurrentCustomer.formGroup">
<div>
Name:
<input type="text" formControlName="CustomerNameControl"
[(ngModel)]="CurrentCustomer.CustomerName"><br /><br />
<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerNameControl'].
hasError('required'))">Customer name is required </div>
Code:
<input type="text" formControlName="CustomerCodeControl"
[(ngModel)]="CurrentCustomer.CustomerCode"><br /><br />
<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerCodeControl'].
hasError('required'))">Customer code is required </div>
<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerCodeControl'].
hasError('pattern'))">Pattern not proper </div>

Amount:
<input type="text" 
[(ngModel)]="CurrentCustomer.CustomerAmount"><br /><br />
</div>
{{CurrentCustomer.CustomerName}}<br /><br />
{{CurrentCustomer.CustomerCode}}<br /><br />
{{CurrentCustomer.CustomerAmount}}<br /><br />
<input type="button" 
value="Click" [disabled]="!(CurrentCustomer.formGroup.valid)"/>
</form>

编写响应式表单。

运行并查看您的验证效果

完成后,您应该能够看到验证效果,如下图所示。

脏、原始、已触摸和未触摸

在此实验中,我们涵盖了“valid”属性和“hasError”函数。“formGroup”还有许多其他属性,您在处理验证时会需要它们。以下是一些重要的。

属性 解释
dirty 此属性指示 Value 是否已修改。
pristine 此属性表示字段是否已更改。
touched 当该控件失去焦点时发生。
untouched 字段未被触摸。

下一篇文章有什么内容?

在下一节中,我们将研究如何使用 Angular 4 创建 SPA 应用程序和验证。要阅读 Learn Angular 的第四部分,请单击此处

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

历史

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