Angular7框架搭建及项目开发【已升级Angular8】

一、介绍

通过本文学习,能够掌握搭建Angular开发环境及快速项目构建。

本文主要使用Angular7.x + Ng-zorro-ant(v7.5.0)组件库进行搭建。Angular框架基础核心原理并不在文中进行太多介绍,主要文档参考请链接至官方中文文档。

代码开发编辑器:Visual Studio Code

所需掌握技术点:ES6、Typescript、html、Less、Rxjs

二、Angular开发环境搭建

安装Angular-CLI

注:在安装Angular-CLI之前,请确保你的设备安装了Node.js v10.9.0以上版本。

  1. 使用npm命令安装angular/cli
npm install -g @angular/[email protected]
  1. 安装成功后,运行以下命令即可查看是否安装成功
ng version / ng v 查看脚手架版本信息
  1. 出现以下画面表示已安装成
     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 7.3.6
Node: 11.12.0
OS: darwin x64
Angular: 
... 

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.13.6
@angular-devkit/core         7.3.6
@angular-devkit/schematics   7.3.6
@schematics/angular          7.3.6
@schematics/update           0.13.6
rxjs                         6.3.3
typescript                   3.2.4

三、创建项目并运行

  1. 运行CLI命令创建,如下所示
ng add ngExample
  1. 接下来,ng new命令会提示你添加初始项目所要包含的特性,默认选择后需等待CLI安装Angular包及其他依赖包。

  2. 项目创建成功后,进入项目根目录运行以下指令,向项目中添加ant组件库

cd ngExample
ng add ng-zorro-antd

输入命令后,选择ng-zorro安装特性,等待安装成功。

  1. 使用VSCode编辑工具,打开项目文件夹,在VSCode终端中运行以下CLI命令
ng serve

在终端中看到Compiled successfully,表示已经成功启动Angular项目,具体参考以下图片中内容:

Angular7框架搭建及项目开发【已升级Angular8】_第1张图片
vscode.png
  1. 在浏览器中访问localhost:4200,目前默认端口为4200,后续有需要可根据实际情况在CLI启动命令中变更端口。

  2. 到此为止,在浏览器中看到以下页面时,整个环境搭建以及简单项目构建已经完成,即将进入本文案例开发。

Angular7框架搭建及项目开发【已升级Angular8】_第2张图片
browser.png

四、项目框架技术点

4.1 目录结构

由于初始化项目目录结构并不能很好的满足开发规则,因此对目录结构进行了优化,如下所示

Angular7框架搭建及项目开发【已升级Angular8】_第3张图片
directory.png
目录 说明
src/app/core 核心组件目录,例如HTTP拦截器、国际化服务等
src/app/layout 层级组件目录,例如主框架页面、页面头部组件等
src/app/routes 业务组件目录,包含主体路由以及各业务模块路由
src/app/shared 共享文件目录,项目中可多次且通用文件可在此目录中声明
src/assets 资源目录,用于存放icon字体、国际化翻译文件、图片等
src/environments 环境配置文件
src/styles 自定义样式文件

除了上述优化后的目录介绍,其余文件介绍请参考Angular中文文档 - 项目文件结构

在tsconfig.json中配置模块引入时转为相对路径

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "src",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "es2015",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ],
    "paths": {
      "@shared": [
        "app/shared"
      ],
      "@shared/*": [
        "app/shared/*"
      ],
      "@core": [
        "app/core"
      ],
      "@core/*": [
          "app/core/*"
      ]
    }
  }
}

例子:

import { SharedModule } from '@shared/shared.module';

app目录

app目录是我们要编写的代码目录,我们写的代码都是放在这个目录。
一个Angular程序至少需要一个模块和一个组件,在我们新建项目的时候命令行已经默认生成出来了。

app
    app.component.ts  // 组件
    app.module.ts    // 模块

4.2 页面主体结构

4.2.1 主体结构开发

layout这个目录在Angular中属于一个模块,所以需要有layout.module.ts文件做承载,且要把该文件引入到根模块app.module.ts中。

  • 在src/app/layout中创建default.component.html模板
  • 在src/app/layout中创建default.component.ts文件
import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'layout-default',
    templateUrl: './default.component.html',
})
export class LayoutDefaultComponent {
}

该页面为上中下布局,中间层的为路由加载位置。

4.2.2 优化页面结构

如何优化页面结构?

根据ng-zorro-ant组件库为我们提供了一些常用的页面布局组件,我们只需要简单步骤就能够替换。

  • 替换default.component.html内容

    
        
  • 用户列表
Home

这几个是ng-zorro-ant组件库所提供的已封装组件。

4.3 自定义组件

4.3.1 组件相关的概念

  • 组件元数据装饰器(@Component)
    简称组件装饰器,用来告知Angular框架如何处理一个TypeScript类.
    Component装饰器包含多个属性,这些属性的值叫做元数据,Angular会根据这些元数据的值来渲染组件并执行组件的逻辑

  • 模板(Template)
    我们可以通过组件自带的模板来定义组件的外观,模板以html的形式存在,告诉Angular如何来渲染组件,一般来说,模板看起来很像html,但是我们可以在模板中使用Angular的数据绑定语法,来呈现控制器中的数据。

  • 控制器(controller)
    控制器就是一个普通的typescript类,他会被@Component来装饰,控制器会包含组件所有的属性和方法,绝大多数的业务逻辑都是写在控制器里的。控制器通过数据绑定与模板来通讯,模板展现控制器的数据,控制器处理模板上发生的事件。

/*这里是从Angular核心模块里面引入了component装饰器*/
import {Component} from '@angular/core';

/*用装饰器定义了一个组件以及组件的元数据  所有的组件都必须使用这个装饰器来注解*/
@Component({
  /*组件元数据  Angular会通过这里面的属性来渲染组件并执行逻辑 */

  // 这是css选择器,表示这个组件可以通过app-user来调用
  selector: 'app-user',
  // 组件的模板,定义了组件的布局和内容
  templateUrl: './user.component.html',
  // 该模板引用less样式
  styleUrls: ['./user.component.less']
})
// UserComponent本来就是一个普通的typescript类,但是上面的组件元数据装饰器告诉Angular,UserComponent是一个组件,需要把一些元数据附加到这个类上,Angular就会把UserComponent当组件来处理
// 这个类实际上就是该组件的控制器,我们的业务逻辑就是在这个类中编写
export class UserComponent implements OnInit {
  
  // 组件初始化时加载
  ngOnInit() {
    console.log('这是用户管理页面');
  }
}

4.3.2 组件绑定数据

  • 首先在user.component.ts中定义一个值title,随后在组件初始化时赋值
export class UserComponent implements OnInit {
  
  title: string = '';
  
  ngOnInit() {
    console.log('这是用户管理页面');
    this.title = ‘用户管理页’;
  }
}
  • 如何使用这个值?在user.component.html页面中使用双括号绑定

{{title}}

4.3.3 双向数据绑定


export class UserComponent {
  value: string;
}

4.4 路由

4.4.1 路由定义

  • management.module.ts中添加
const routes: Routes = [
    {
        path: '', component: ManagementComponent, data: { title: '管理' },
        children: [
            { path: 'user', component: UserComponent, data: { title: '用户列表' } },
        ]
    }
];
imports: [
    RouterModule.forChild(routes)
],

4.4.2 路由模块引入

  • 把刚定义好的路由ManagementModule放到routes-routing.module.ts
const routes: Routes = [
    { path: '', component: LayoutDefaultComponent,
      children: [
        { path: 'management', loadChildren: 'app/routes/management/management.module#ManagementModule' },
      ],
    }
];

4.4.3 路由使用

  • routeLink
  • 用户菜单
  • 4.5 服务及创建观察者对象

    什么是服务?定义公共的方法,使得方法在组件之间共享调用

    Angular 把组件和服务区分开,以提高模块性和复用性。 通过把组件中和视图有关的功能与其他类型的处理分离开,你可以让组件类更加精简、高效。

    4.5.1 创建服务并注册引用

    • management目录中创建一个名为management.service.ts文件,初始内容如下
    import { Injectable } from '@angular/core';
    
    @Injectable({
        providedIn: 'root'
    })
    export class ManagementService {
        constructor() {
    
        }
    }
    
    • 服务注册

    management.module.ts中注册

    import { ManagementService } from './management.service';
    
    @NgModule({
        providers: [ManagementService]
    })
    
    
    • 服务引用

    user.component.ts中引用

    import { ManagementService } from '../management.service';
    
    constructor(
        private managementService: ManagementService
    ) { }
    

    4.5.2 创建可观察对象

    可观察对象支持在应用中的发布者和订阅者之间传递消息。 在需要进行事件处理、异步编程和处理多个值的时候,可观察对象相对其它技术有着显著的优点。

    可观察对象是声明式的 —— 也就是说,虽然你定义了一个用于发布值的函数,但是在有消费者订阅它之前,这个函数并不会实际执行。 订阅之后,当这个函数执行完或取消订阅时,订阅者就会收到通知。

    • Observable声明
    export class ManagementService {
        constructor(private http: HttpClient) {
    
        }
        getUsers(): Observable {
            let u: User = new User();
            u.name = 'zhangsan';
            u.email = "[email protected]";
            let users = [{ name: 'zhangsan', email: 'xxx' }];
            users.push(u);
            return of(users);
        }
    
    }
    class User {
        public name: string;
        public email: string;
    }
    

    4.6 订阅

    当创建了观察者对象后,需要通过subscribe订阅的方式来实现功能。

    list;
    
    ngOnInit() {
        this.managementService.getUsers().subscribe(data => {
            this.list = data;
        });
    }
    
    • 订阅成功后使用ng-zorro-ant表格组件现实,其中涉及到ng指令*ngIf,指令具体使用方式请参考Angular中文文档。
    
        
          
            Name
            Email
          
        
        
          
            {{ item.name }}
            {{ item.email }}
          
        
    
    
    Angular7框架搭建及项目开发【已升级Angular8】_第4张图片
    table1.png

    4.7 表单

    本文中使用了ng-zorro-ant封装的表单组件,是具有数据收集、校验和提交功能的表单。

    validateForm: FormGroup;
    
    constructor(
        private fb: FormBuilder
    ) { }
    
    ngOnInit() {
        
        // 
        this.validateForm = this.fb.group({
            userName: [null, [Validators.required]],
            email: [null, [Validators.required]]
          });
    
    }
    

    在html中,

    
    
    
    

    利用this.validateForm.controls[’userName‘].value可获取到输入的值。

    ** FormGroup ** : 把每个子 FormControl 的值聚合进一个对象,它的 key 是每个控件的名字。 它通过归集其子控件的状态值来计算出自己的状态。

    4.8 HTTP拦截器

    其作用是可拦截HTTP请求返回响应状态码,针对状态码进行处理。

    • 在core/net文件夹中创建以下拦截器
    import { Injectable, Injector } from '@angular/core';
    import { Router } from '@angular/router';
    import { HttpInterceptor, HttpRequest, HttpHandler, HttpErrorResponse, HttpEvent, HttpResponseBase } from '@angular/common/http';
    import { Observable, of, throwError } from 'rxjs';
    import { mergeMap, catchError } from 'rxjs/operators';
    import { NzMessageService, NzNotificationService } from 'ng-zorro-antd';
    import { _HttpClient } from '@delon/theme';
    import { environment } from '@env/environment';
    import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
    
    const CODEMESSAGE = {
      200: '服务器成功返回请求的数据。',
      201: '新建或修改数据成功。',
      202: '一个请求已经进入后台排队(异步任务)。',
      204: '删除数据成功。',
      400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
      401: '用户没有权限(令牌、用户名、密码错误)。',
      403: '用户得到授权,但是访问是被禁止的。',
      404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
      406: '请求的格式不可得。',
      410: '请求的资源被永久删除,且不会再得到的。',
      422: '当创建一个对象时,发生一个验证错误。',
      500: '服务器发生错误,请检查服务器。',
      502: '网关错误。',
      503: '服务不可用,服务器暂时过载或维护。',
      504: '网关超时。',
    };
    
    /**
     * 默认HTTP拦截器,其注册细节见 `app.module.ts`
     */
    @Injectable()
    export class DefaultInterceptor implements HttpInterceptor {
      constructor(private injector: Injector) { }
    
      get msg(): NzMessageService {
        return this.injector.get(NzMessageService);
      }
    
      private goTo(url: string) {
        setTimeout(() => this.injector.get(Router).navigateByUrl(url));
      }
    
      private checkStatus(ev: HttpResponseBase) {
        if (ev.status >= 200 && ev.status < 300) return;
    
        const errortext = CODEMESSAGE[ev.status] || ev.statusText;
        this.injector.get(NzNotificationService).error(
          `请求错误 ${ev.status}: ${ev.url}`,
          errortext
        );
      }
    
      private handleData(ev: HttpResponseBase): Observable {
        // 可能会因为 `throw` 导出无法执行 `_HttpClient` 的 `end()` 操作
        if (ev.status > 0) {
          this.injector.get(_HttpClient).end();
        }
        this.checkStatus(ev);
        // 业务处理:一些通用操作
        switch (ev.status) {
          case 200:
            break;
          case 401: // 未登录状态码
            // 请求错误 401: https://preview.pro.ant.design/api/401 用户没有权限(令牌、用户名、密码错误)。
            (this.injector.get(DA_SERVICE_TOKEN) as ITokenService).clear();
            this.goTo('/passport/login');
            break;
          case 403:
          case 404:
          case 500:
            break;
          default:
            if (ev instanceof HttpErrorResponse) {
              console.warn('未可知错误,大部分是由于后端不支持CORS或无效配置引起', ev);
              return throwError(ev);
            }
            break;
        }
        return of(ev);
      }
    
      intercept(req: HttpRequest, next: HttpHandler): Observable> {
        // 统一加上服务端前缀
        let url = req.url;
        if (!url.startsWith('https://') && !url.startsWith('http://')) {
          url = environment.SERVER_URL + url;
        }
    
        const newReq = req.clone({ url });
        return next.handle(newReq).pipe(
          mergeMap((event: any) => {
            // 允许统一对请求错误处理
            if (event instanceof HttpResponseBase)
              return this.handleData(event);
            // 若一切都正常,则后续操作
            return of(event);
          }),
          catchError((err: HttpErrorResponse) => this.handleData(err)),
        );
      }
    }
    
    
    • 拦截器使用

    在Angular框架@angular/common/http中,提供了HTTP_INTERCEPTORS,我们可以通过自己定义的拦截器触发,做出对应业务处理。

    放到app.module.ts

    providers: [
        { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true}
    ]
    

    4.9 管道

    什么是管道?Angular中的管道(pipe)是用来对输入的数据进行处理,如大小写转换、数值和日期格式化等。

    4.9.1 日期格式化管道

    管道以 | 符号右边表示。

    {{today | date:'yyyy-MM-dd HH:mm:ss' }}

    4.9.2 自定义管道

    • 自定义管道的步骤:

    使用 @Pipe 装饰器定义 Pipe 的 metadata 信息,如 Pipe 的名称 - 即 name 属性
    实现 PipeTransform 接口中定义的 transform 方法

    import { Pipe, PipeTransform } from '@angular/core';
    
    [@Pipe](/user/Pipe)({ name: 'welcome' })
    
    export class WelcomePipe implements PipeTransform {
      transform(value: string): string {
        if(!value) return value;
        if(typeof value !== 'string') {
          throw new Error('Invalid pipe argument for WelcomePipe');
        }
        return "Welcome to " + value;
      }
    } 
    
    • 自定义管道使用

    {{ 'ngExample' | welcome }}

    五、Angular性能优化

    5.1 多模块懒加载

    优化前我们工程就一个主模块文件(app.module.ts),路由跳转各页面其实都属于该模块一部分,假如路由对应各页面都是子组件的话,编译时都会被打包到同一个文件。

    拆分模块,如下所示

    const routes: Routes = [
        { path: '', component: LayoutDefaultComponent,
          children: [
            { path: '', redirectTo: 'index/welcome', pathMatch: 'full' },
            { path: 'index', loadChildren: 'app/routes/index/index.module#IndexModule' },
            { path: 'management', loadChildren: 'app/routes/management/management.module#ManagementModule' },
          ],
        }
    ];
    

    可以看到写法明显不同,每个路由页面其实都是一个单独模块,然后在编译时每个模块都会单独编译成一个文件。而且路由到某个页面时,才会加载该模块js文件。

    运行时可以从控制台发现以下信息,表示拆分成功

    chunk {app-routes-index-index-module} app-routes-index-index-module.js, app-routes-index-index-module.js.map (app-routes-index-index-module) 6.02 kB  [rendered]
    chunk {app-routes-management-management-module} app-routes-management-management-module.js, app-routes-management-management-module.js.map (app-routes-management-management-module) 6.78 kB  [rendered]
    

    5.2 打包优化

    在打包时添加命令
    --prod –aot 优化编译方式。

    六、心得体会

    从最开始的技术选型以及到后续慢慢深入学习了解Angular框架之后,每一个疑问难点解决过程走过来,Angular都能为我带来惊喜。

    Angular 以M(model数据)V(view视图/表现层)C(controller控制器/业务逻辑)为基础,降低前端重复工作的劳动量,扩展了HTML功能,组件复用性高。

    其中以双向绑定数据为核心,Angular开发过程中基本上是在操作数据。

    七、学习网站分享

    Angular-CLI

    ant组件库

    Angualr中文文档

    八、此文档项目源码

    目前项目源码Angular框架已升级至Angular8

    Github链接

    你可能感兴趣的:(Angular7框架搭建及项目开发【已升级Angular8】)