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

Angular 4 导航 - 简介

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2017年9月13日

MIT

15分钟阅读

viewsIcon

24677

downloadIcon

342

Angular 4 的基本介绍。

Angular 4 Navigation Screenshot

引言

当我开始学习 Angular 4 时。令我惊讶的是它与 Angular 1.x 有多大的不同。Angular 4 最困难的方面是整个应用程序的架构都发生了变化。尽管我曾认为自己对 Angular 1.x 相当熟悉,但开始学习 Angular 4 仍然有些困难。本教程的重点是关于如何设置一个基本的 Angular 4 应用程序以实现页面导航。我还想讨论我注意到的一些陷阱。

运行 Hello World 示例项目

以下是我为使这个简单的客户端应用程序工作而采取的关键步骤。以下是步骤摘要:

  • 安装最新的 Node JS。
  • 获取 Hello World 示例应用程序。
  • 安装所有必需的 NPM 包。
  • 运行示例应用程序。
  • 更改示例应用程序以获得新设计
  • 运行新应用程序进行测试。

安装 Node.JS

当我最初开始学习 Angular 2/Angular 4 应用程序开发时,我安装了一个非常旧版本的 Node.JS。它可能已经有九个月了。当我在 Angular 2 或 Angular 4 应用程序开发中使用它时,我看到了奇怪的错误,在搜索引擎上找不到答案。经过一番挣扎,我放弃了,安装了最新版本,一切都正常了。所以,如果你看到奇怪的错误并且找不到问题所在,请考虑你使用的 Node.JS 版本。如果必须,请升级并查看是否能解决你的问题。

nodejs.org 上的下载页面是:https://node.org.cn/en/download/

获取 Hello World 示例应用程序

官方 Angular 网站有一个基本的演练教程:The Hero Editor。我认为这是一个很棒的教程。您可以在网上找到的关于 Angular 2/Angular 4 的最佳基础教程。一旦您完成了所有教程,您将不再是新手,并且可以充分地使用 Angular 4。

不管怎样,我跑题了。对我来说,开始使用 Angular 4 最简单的方法是获取一个可工作的 Hello World 示例应用程序。对应用程序架构进行一些定制,我就可以得到我自己的 Angular 应用程序了。

官方 Angular 4 教程提供了一个 Hello World 示例项目,我使用的是这个项目。示例应用程序的链接可以在这里找到:https://angular.io/guide/setup。请遵循此设置指南来配置您的开发环境。

下载后,将其解压缩并将解压后的源代码放在一个有意义的文件夹中,最好是文件夹名称较短,例如 C:\sample\Hello。当您开始运行代码时,下载的 node_modules 将具有非常长的文件名。有时,如果基目录有一个长文件名,这可能会有问题。

安装最新的 NPM 包

一旦您从 angular.io 获取了 Hello World 示例,您就可以运行 npm 命令来安装项目所需的所有 npm 包。首先,您需要解压缩示例应用程序。转到解压后的文件目录,里面应该有一个 src 目录。进入这个 src 目录,然后运行以下命令:

npm install 

此命令将下载并安装示例项目所需的所有必需包。获取所有包需要一些时间。我会等到所有包都下载并安装完毕,然后进行下一步。

如果您想知道包在哪里定义,请转到 src 上方的目录,您会找到一个名为 package.json 的文件。您可以用文本编辑器打开它,在其中,您会发现两个部分:

  1. dependencies(依赖项),以及
  2. devDependencies(开发依赖项)

这些是 dependencies(依赖项)可以被修改的地方。

我在我的示例应用程序中使用了 bootstrap。因此,我将两个新的 dependencies:bootstrap(版本 3.3.7 或更高版本)和 jquery(版本 3.2.1 或更高版本)添加到 dependencies 部分的末尾。

"dependencies": {
   "@angular/animations": "~4.2.0",
   "@angular/common": "~4.2.0",
   "@angular/compiler": "~4.2.0",
   "@angular/compiler-cli": "~4.2.0",
   "@angular/core": "~4.2.0",
   "@angular/forms": "~4.2.0",
   "@angular/http": "~4.2.0",
   "@angular/platform-browser": "~4.2.0",
   "@angular/platform-browser-dynamic": "~4.2.0",
   "@angular/platform-server": "~4.2.0",
   "@angular/router": "~4.2.0",
   "@angular/tsc-wrapped": "~4.2.0",
   "@angular/upgrade": "~4.2.0",
   "angular-in-memory-web-api": "~0.3.2",
   "core-js": "^2.4.1",
   "rxjs": "5.0.1",
   "systemjs": "0.19.39",
   "zone.js": "^0.8.4",
   "jquery": "^3.2.1",
   "bootstrap": "^3.3.7"
} 

修改了 package.json 文件后,我再次运行了相同的 npm 命令。它会添加新指定的包。

请注意,默认情况下,节点包(称为 node_modules)安装在基目录(src 的父目录)中。我认为这与 tsconfig.json 中的相对路径 ../node_modules/@types/ 有关。您只需要关心相对路径,这将在下一节中进行描述。

运行示例项目

要运行示例项目,您只需要执行以下操作:

npm start 

输入命令后,控制台会输出大量信息,默认浏览器会加载并显示 Hello World 的示例输出页面。即使我更改了 package.json 文件,也不应该影响原始的示例项目。

如果浏览器中显示了 Hello World,那么您已经为本教程的第二部分做好了准备,该部分将介绍如何修改以实现导航和一些基本的 Angular 4 用户交互功能。

Angular 4 导航和基本 UI 交互

在本节中,我将介绍我对 Hello World 项目所做的更改,以使页面导航正常工作。为了使这个示例应用程序更有意义,我还包含了一个小的 UI 交互。我还将解释它是如何工作的。

为什么需要导航?

当我研究这个问题时,我认为 Angular 4 中的页面导航是一个有趣的挑战。而且学习它非常重要。所以我决定利用这个机会来学习它的基本知识。Angular 4 用于创建单页应用程序。但这并不意味着应用程序只由一页组成。这将使应用程序非常拥挤。更好的方法是将应用程序的功能拆分成多个功能,每个功能由其自己的页面提供。这个功能自 Angular 1.x 起就已支持。我以为会很容易掌握。结果却相当复杂。

我首先阅读了 angular.io 上的教程。然后我阅读了 angular.io 上一个项目的源代码。结果证明这很复杂。通过那段源代码和其他一些资源(我很抱歉,我已经忘记在哪里找到它们了),我得以使其工作。在下一节中,我将从示例应用程序的入口开始。后续章节将进一步讨论设计。

核心

Angular 2/Angular 4 应用程序的核心是一个名为 main.ts 的文件。这个文件将被编译成 main.js,而 main.js 将被 index.html 文件包含。main.ts 的内容如下:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule); 

这个文件所做的就是引导一个名为 AppModule 的类,该类位于一个名为 app.module.ts 的文件中。您可以在上面的代码片段中看到引用 -- ./app/app.module。它指明了文件所在的位置,在 app 的子目录中,有 app.module.ts 文件。

app.module.ts 是将所有其他必需模块聚合在一起的文件。此文件的内容如下:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { AboutComponent } from './components/about.component';
import { HomeComponent } from './components/home.component';

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

我必须导入 AppRoutingModuleAppComponentAboutComponentHomeComponent。这些都是我创建的所有模块和组件。此文件中只有一个类,名为 AppModule。该类有一个 @NgModule 装饰器,类似于 .NET Framework 中的属性和 Java 中的注解。该装饰器执行三个不同的操作:

  • 它将 AppComponent 类标记为引导类——启动类。
  • 它将三个类标记为控制器。
  • 它导入了两个模块。其中一个模块是路由模块。

启动类

AppComponent 类被标记为启动类。它定义在一个名为 app.component.ts 的文件中。该文件的内容如下:

import { Component } from "@angular/core";
import { Routes } from '@angular/router';
import { HomeComponent } from './components/home.component';
import { AboutComponent } from './components/about.component';

@Component({
  selector: 'my-app',
  templateUrl: './templates/app.tpl.html'
})
export class AppComponent { }

此文件的顶部导入了四个不同的组件。前两个来自 Angular 库。它们是必需的,因为:

  1. AppComponent 类使用了 @Component 装饰器;
  2. AppComponent 使用的模板引用了 Routes 类中的 routerLinkrouter-outlet

文件后半部分是 AppComponent 的定义。这个类与前面几节中的两个类一样,没有属性或方法。它有一个名为 @Component 的装饰器。对于这个类,@Component 装饰器有两个属性,一个称为 selector。另一个称为 templateUrlselector 属性定义了在 index 页面上哪个元素将被替换为新的 UI 模板。templateUrl 定义了将使用哪个页面模板。本质上,AppComponent 是控制器,而 templateUrl 引用控制器可以渲染的视图。

选择器被赋值为 my-app。那么这个 my-app 选择器在哪里使用?您可以在 index.html 中找到它。当 index.html 加载成功时,它将首先启动 main.js(由 main.ts 转换而来),然后加载 app.module.js(由 app.module.ts 转换而来)。如果您还记得前几节的内容,加载的模块之一是 app.components.js(由 app.components.ts 转换而来)。这就是 AppComponent 如何被引导为启动类。

现在,看一下 AppComponent 的模板文件:

<div class="container">
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" 
      data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">Brand</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav navbar-right">
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" 
          aria-haspopup="true" aria-expanded="false">Navigation 
          <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a [routerLink]="['/']">Home</a></li>
            <li><a [routerLink]="['/about']">About</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<router-outlet></router-outlet>
</div>

这个页面模板可以看作两部分。上半部分是一个导航菜单。有很多 html 代码用于创建这个导航菜单。它真正有用的部分是两个锚点:

   <li><a [routerLink]="['/']">Home</a></li>
   <li><a [routerLink]="['/about']">About</a></li>

这两个锚点定义了我的示例项目中可以导航的不同页面。一个是主页(通过 / 导航),另一个是关于页(通过 /about 导航)。模板 html 文件中的标记并没有让导航生效。使之生效的是我的 AppRoutingModule。这将在下一节讨论。

路由模块

路由模块定义在 app-routing.module.ts 文件中。类名为 AppRoutingModule。在我最终将这个类与项目其余部分集成时,我学到了一件事:文件名很重要。我相信 Angular 2/Angular 4 是根据命名约定在文件系统中本地搜索的。这是我到达这一点时遇到的一些困难。我尝试了许多不同的名称作为路由模块文件,并将其包含在 app.module.ts 中。没有一个起作用,直到我意识到文件名应该是 app-routing.module.ts,Angular 可能会将文件中的类解释为 AppRoutingModule。这里有一个提示,如果您的项目出错,而其他一切看起来都正确,请检查您的文件名。转换规则是:

  • 文件名应全部小写。
  • 文件名应与类名大致相同。例如,当我导出名为 AppRoutingModule 的类时,文件名将包含 approutingmodule
  • 根据导出的文件是模块还是组件。文件名将包含 .module.component,后跟文件扩展名 .ts
  • 类名包含多个单词,这些单词将小写并用连字符分隔。例如,AppRoutingModule,单词 AppRouting 将在文件名中转换为 app-routing

还有相对路径的问题。有时,您可以尝试通过相对路径从当前目录中查找文件,有时,您也可以只使用绝对路径。我将展示两者的示例。

app-routing.module.ts 的内容如下:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AboutComponent } from './components/about.component';
import { HomeComponent } from './components/home.component';

export const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];
@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes
    )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

文件有三个不同的部分:

  • 第一部分将多个模块或组件导入到此文件中。
  • 第二部分将导出一个名为 appRoutes 的数组。在此数组中,有两个元素(对象)定义了不同的路由。
  • 第三部分创建 AppRoutingModule 模块类。该类由 @NgModule 装饰。在装饰器内部,appRoutes 用于与 RouterModule 绑定,该 RouterModule 与导出的类 AppRoutingModule 相关联。

这个类展示了一个非常基本的路由模块类。定义路由模块有更复杂的方法,您可以在 angular.io 的示例项目中看到。文件最重要的部分是 appRoutes 数组的声明。在此声明中,每个 URL 路径都与一个组件相关联。组件,如果您还记得之前的内容,是一个与视图关联的控制器。因此,在这种情况下,路由模块使用此 appRoutes 数组将 URL 路径路由到特定的控制器,而该控制器又管理关联的视图。

接下来,我们将看看这些控制器是什么样的。

控制器和视图

我为这个导航示例创建了两个页面。这两个页面的控制器是:

  • HomeComponent
  • AboutComponent

在 Angular 4/Angular 2 中,不再有控制器概念。取而代之的是,控制器更像是一个组件。可以通过 @Component 装饰器,使用 @Component 装饰器的 templatetemplateUrl 属性将视图与组件关联起来。您可以在上一节“启动类”中看到这一点。在本节中,我列出了索引组件的代码。用于导航页面的两个组件看起来非常相似。这是 HomeComponent 的源代码:

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

@Component({
  templateUrl: 'app/templates/home.tpl.html'
})
export class HomeComponent {
   public sunshine: string;
   
   constructor() {
      this.sunshine = "Cowboy Jackie";
   }
}

HomeComponent 的工作方式是,@Component 装饰器将视图绑定到这个 HomeComponenttemplateUrl 定义了视图的位置。我个人喜欢将视图放在一个单独的文件中,这使得组件类文件更整洁。有很多例子是将视图文件放在同一个组件类文件中。这没有错,但我个人更喜欢将视图分离到另一个文件中。

HomeComponent 类有一个 public 属性和一个构造函数。在该构造函数中,public 属性被初始化为字符串值“Cpwboy Jackie”。我这样做是为了测试我是否能在视图中显示属性 sunshine 的值。我想指出,将属性声明为 public 是一种不好的做法。我应该使用属性访问器。

以下是 HomeComponent 的视图:

<div class="row">
   <div class="col-xs-12 col-sm-offset-1 col-sm-10">
      <div class="row">
         <div class="col-xs-12">
         <h3>Home</h3>
         <hr>
         <p>This is the home. Hello <span style="color: darkgreen">
         {{sunshine}}</span>!</p>
         </div>
      </div>
   </div>
</div>

在上面的 html 代码中,属性 sunshine 的使用方式是 {{sunshine}}。双花括号是 Angular 将模型变量从控制器(也称为组件类)绑定到视图的方式。请注意,这种绑定是双向的。如果视图中的值因用户交互而改变,控制器(组件类)中的值也会相应反映。为了说明这一点并展示用户交互,我创建了另一个控制器,名为 AboutComponent

我在这关于页面上尝试做的是,页面上有一个按钮。还有一个计数器,初始化为一。当用户点击按钮时,计数器会增加一。值的变化会显示在页面上。

以下是 AboutComponent 的代码:

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

@Component({
  templateUrl: 'app/templates/about.tpl.html'
})
export class AboutComponent {
   public viewCount: number;
   
   constructor() {
      this.viewCount = 1;
   }
   
   public incrementViewCount() {
      this.viewCount = this.viewCount + 1;
   }
}

AboutComponent 的源代码在类定义方面与 HomeComponent 的源代码非常相似。这个 AboutComponent 稍微复杂一些。首先,我定义了一个名为 viewCountpublic 属性。它的类型是 number。再次,我必须强调将此属性声明为 public 是一种不好的做法,应该使用属性访问器。

在构造函数中,viewCount 属性被设置为一。还有一个名为 incrementViewCountpublic 方法。此方法会将 viewCount 增加一。该方法由视图使用,具体来说,它是按钮点击事件的回调方法。每当按钮被点击时,该方法就会被调用,并将 viewCount 属性增加一。

以下是 AboutComponent 使用的 About 视图的源代码:

<div class="row">
   <div class="col-xs-12 col-sm-offset-1 col-sm-10">
      <div class="row">
         <div class="col-xs-12">
         <h3>About This Site</h3>
         <hr>
         <p>This is an about page. This page has been viewed {{viewCount}} times.</p>
         </div>
         
         <div class="col-xs-12">
         <p>
         Click <button class="btn btn-success" 
         (click)="incrementViewCount()">Increment</button> to increment view counts;
         </p>
         </div>
      </div>
   </div>
</div>

这是一个简单的视图。它有两个部分,上半部分是句子“This is an about page. This page has been viewed X times.”。值 X 是通过与 viewCount 属性绑定显示的。模型绑定是通过:{{viewCount}} 完成的。

按钮(名为 Increment)有一个点击事件,并与控制器类 AboutComponent 的方法 incrementViewCount() 绑定。这是通过 (click)="incrementViewCount()" 完成的。

测试示例应用程序

在编译和运行示例应用程序之前,您必须进入此示例项目的 src 目录。并找到以下两个文件:

  • systemjs.config.jstxt
  • systemjs-angular-loader.jstxt

将这两个文件重命名为:

  • systemjs.config.js
  • systemjs-angular-loader.js

我必须重命名这两个文件并将其重命名回来的原因是,js 文件不允许通过电子邮件服务器传输。

文件重命名后,您可以运行以下命令来安装所有 node.js 包:

npm install

然后,要运行示例应用程序,请使用以下命令:

npm start 

摘要

就是这样。在本教程中,我回顾了此简单 Angular 4 示例应用程序的所有重要方面。我的示例比简单的 Hello World 示例应用程序要复杂一些。此应用程序具有两页导航。在一页上,组件的数据模型与视图进行了简单的绑定。在另一页上,进行了简单的数据模型双向绑定。这个示例应用程序为我提供了一个基线,以便我能够开发更复杂的应用程序。

这个示例项目有很多部分我没有提供解释。我也不理解其中的一些部分。而且这并不重要,因为我不需要修改这些部分,至少现在还不需要。我相信,如果我仔细阅读手册,我应该能够理解它们的作用。我相信您也可以做到。

有一件事确实困扰我。我无法在 tnpm lite 服务器之外运行此示例应用程序。我相信有一种方法。我需要配置 webpack 并将其作为构建过程的一部分。我应该能够将所有必需的 JavaScript 代码打包到一个小包中,并可以将其添加到 ASP.NET 应用程序或 Java WAR 文件中。我不知道如何实现这一点。这是我以后会研究的。我认为这是可以解决的。

不管怎样,希望您喜欢本教程。祝您学习 Angular 4 顺利。

历史

  • 2017年9月11日 - 初稿
  • 2023年1月19日 - 修复了损坏的链接
© . All rights reserved.