详解关于Angular4 ng-zorro使用过程中遇到的问题


Posted in Javascript onDecember 05, 2018

写在前面

由于现在网络上Angular 4的相关技术文档不是很充分,我写出这个采坑的记录文档,一方面是想给自己在项目中遇到的各种问题与个人的理解记录下来,另一方面也想着某些坑大家可能也会遇到,也可以给道友做一个参考。文档中的很多地方多有不足,后期我会慢慢完善,也希望道友们能够及时指出文档中不正确的与可以优化的地方。

我计划将该帮助文档分为4个章节:

章节一:

关于angular 4 + ng-zorro在基础布局与模块拆分上的一些问题与操作步骤

章节二:

angular 4 引入路由=> 组件模块化#module模块化=>  路由模块化(路由按需加载)

章节三:

引入拦截器,统一管理请求与相应=>引入http服务进行通讯=>引入service服务与后台进行通讯=>拆分service服务=> 应用观察者模式对数据进行发布与订阅

章节四:

项目打包=>优化

============================= Begin ===============================

章节一:关于angular 4 + ng-zorro在基础布局与模块拆分上的一些问题与操作步骤

在使用阿里爸爸推出的Ng-zorro前,希望你先确保本地的angular-cli版本是最新的版本,目前最新的版本为1.6.3(2018/1/10) *兼容问题可能会导致后期项目打包后部门js丢失

如果你本地已经全局安装了cli或者已经使用相对较旧的版本创建了angular 的项目,那么你可以按照下面的命令去更新你本地与项目中的cli版本去兼容ng-zorro:

首先需要先卸载本地的angular-cli安装包:

npm uninstall -g angular-cli
npm uninstall --save-dev angular-cli

在全局安装最新版本的cli包:

npm uninstall -g @angular/cli
npm cache clean
npm install -g @angular/cli@latest

你可以通过cmd命令行,使用 ng -v 去看到本地目前cli的版本。如果你已经安装了最新的版本,你可以使用新版本的ng命令: [ng new "项目名称" ]来创建一个新的angular 项目。如果你已经有angular项目了,那你需要去更新项目中的cli版本。具体的命令如下:

rmdir -rf node_modules dist 
npm install --save-dev @angular/cli@latest
npm install

如果你完成了上面的操作,你可以打开package.json来看到你项目中的cli版本已经更换到了最新版本了。

在使用ng-zorro的过程中,需要注意两点:

Ng-zorro并不能一次引入在多组件里进行使用,如果你的项目中存在子module,相关的依赖包需要在子module里进行引入。需要注意的是,你必须在module里通过forRoot()方法去使用。

//主module
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
NgZorroAntdModule.forRoot(),
BrowserAnimationsModule
]

在子module里,就不再需要forRoot()方法了:

//子module
imports: [
CommonModule,
HttpClientModule,
NgZorroAntdModule
]

当你引入了所需的这些文件后,你就可以开始使用ng-zorro了。

章节二:angular 4 引入路由 => 组件模块化#module模块化 =>  路由模块化(路由按需加载)

 2.1  angular 4 引入路由

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { NgZorroAntdModule } from 'ng-zorro-antd';
import { RouterModule, Routes } from '@angular/router';
import {HashLocationStrategy , LocationStrategy} from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
//主module
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
NgZorroAntdModule.forRoot(),
BrowserAnimationsModule
]
//子module
imports: [
CommonModule,
HttpClientModule,
NgZorroAntdModule
],

angular 导入module了之后,一般情况下会将路由单独放在一个文件中进行引入。你需要在主module中进行引入,然后在主module里进行导出,如果你有子module,那么你需要在子module中进行导入,在子module中进行导出,因为Routermodule作为作为管理路由的工作,会将多个模板导入到同一模板中。如果你的项目中需要将路由文件拆分或者如要按需加载与懒加载相关功能,那么这时候你可能需要将路由进行相互关联,在Vue中你可以通过ES6的一些语法进行链接,而angular 4提供了loadChildren来进行响应的相应的链接。具体的代码如下:

imports: [
 BrowserModule,
 FormsModule,
 HttpClientModule,
 NgZorroAntdModule.forRoot(),
 BrowserAnimationsModule,
 EventAnalysisModule,
 RouterModule.forRoot(
  appRoutes
 )
 ],
 exports: [
 RouterModule
 ],
imports: [
  CommonModule,
  FormsModule,
  ReactiveFormsModule ,
  NgxEchartsModule,
  HttpClientModule,
  NgZorroAntdModule,
  RouterModule.forChild(EVENTROUTES)
 ],
 exports: [
  RouterModule
 ],

routerModule 包含两个关键方法,forRoot(),forChild()

这两个方法,做为控制多个模块在同一模块进行展示,分别在父子module中起到了关键作用,这也是LoadChildren生效的关键步骤。

//路由配置文件
 {
  path: 'index',
  component: NzDemoLayoutTopSide2Component,
  children: [
   {
    path: 'event',
    loadChildren: './event/eventAnalysis.module#EventAnalysisModule'
   }
  ]
 },
//EventAnalysisModule 路由部分
 {
  path: 'eventAnalysis',
  component: EventAanlysisComponent,
  children: [
   {
    path: 'overview',
    component: OverviewComponent
   }, {
    path: 'CreditEvaluation',
    component: CreditEvaluationComponent
   }, {
    path: 'loanHistroy',
    component: LoanHistroyComponent
   }, {
    path: 'userInfo',
    component: UserInfoComponent
   }
  ]
 }

如果你的项目比较大,需要将路由进行模块化或者进行一些懒加载或者按需加载的相关功能,你需要通过loadChildren将路由进行联系。由于loadChildren是需要依赖到最外层路由导入的文件中的,所以你需要将你导入的模块的路径写在路由参数中,而不是通过import的形式导入,并且你需要使用#去分割路径,和导入的模块名。

章节三:引入拦截器,统一管理请求与相应

如果你使用axios,你可能用过他的拦截功能,允许我们把身份认证,错误处理和服务器状态码等相关问题进行统一处理,而不需要在每个页面去单独处理,angular在实现拦截器功能的过程中也非常简单,只需要实现HttpInterceptor接口就可以了。

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  const clonedRequest = req.clone({
   
   headers: req.headers.set('Content-Type', 'text/plain;charset=UTF-8')
  });

而intercept方法则有两个参数,一个是 request,一个是next来调用下一个"中间件"。

按照angular 官网文档的写法,request有一个clone方法,可以去处理我们的请求,并在请求中加入响应的参数,如token, header, 浏览器cookie等

最后,你需要将你的请求参数传递到下一个中间件,而这里则是在return之后进行操作,像这样:

return next.handle(clonedRequest)

在响应处理的过程中,包含多种情况,你需求将正确的请求返回到相应的组件,将异常的请求进行统一处理,而这个过程则是一种observable模式,我们需要用mergeMap, do等rxjs操作符来进行处理。

return next.handle(clonedRequest)
   .mergeMap((event: any) => {
    // 处理异常
 reurn bservable.create(Observable => Observable.next(event));
   })
   .catch((res: HttpResponse<any>) => {
 
    return Observable.throw(res);
   })

使用catch进行捕获,返回到组件中。下面是整个拦截器的代码,需要的话可以进行引入,当然,你还需要现在主Module中进行引入,才能够正常生效:

import { HTTP_INTERCEPTORS } from '@angular/common/http';
 
 providers: [MyService, 
    {
    provide: LocationStrategy, 
    useClass: HashLocationStrategy
    },
    {
    provide: HTTP_INTERCEPTORS,
    useClass: NoopInterceptor,
    multi: true,
    }, 
    ApiModule]

拦截器的代码:

import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/mergeMap';
// thorw方法需要单独引入
import 'rxjs/add/observable/throw';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse} from '@angular/common/http';
 
@Injectable()
export class NoopInterceptor implements HttpInterceptor {
 
 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  const clonedRequest = req.clone({
   
   headers: req.headers.set('Content-Type', 'text/plain;charset=UTF-8')
  });
  // console.log("new headers", clonedRequest.headers.keys());
  return next.handle(clonedRequest)
   .mergeMap((event: any) => {
    // if (event instanceof HttpResponse) {
    //  return Observable.create(Observable => Observable.error(event));
    // }
    return Observable.create(Observable => Observable.next(event));
   })
   .catch((res: HttpResponse<any>) => {
 
    return Observable.throw(res);
   })
 }
}

关于mergeMap和整个拦截器的用法,sf上的大神们也进行了详细的说明:

引入http服务进行通讯

当你引入angular的拦截器之后,你就可以统一管理所以请求的请求头,并且可以集中处理所有请求的响应体和异常情况了。那么http请求就变的非常简单了。关于请求的写法,官网和网上有很多的例子,你也可以封装请求方法来进行使用。

引入service服务与后台进行交互

在使用angular4的时候,我想将service做为存储公共数据的地方,那么不同组件的公共的数据和参数,可以存储在service中,那如果共用的数据总有某些场景下不是最新的,既然是这样,为什么不按照官方的demo那样,将数据源放在service中,之后通过订阅或者promise的形式去拿到数据呢,这样不同组件在使用一些共用数据的情况下,可以保证是最新数据,使用起来也更方便了。

既然提到了订阅,就不得不说观察者模式了。观察者模式又被称为发布订阅模式。它定义了一种一对一对多的关系网络。简单来说就是让多个观察者去观察一个对象,当被观察对象发生任何改变的时候,所有订阅了他的观察者们都会及时的收到消息,并及时得到更新。这种感觉很像订阅报纸一样,订阅报纸后,每当有新报纸出版都会送到你手里,让你知道最新的消息,但是如果你取消订阅报纸,那么你就不会收到最新版的报纸了。那么这两个角色被观察者和观察者们用什么来表示呢?其实就是subject与observer。关于subject与observer在使用上,sf上面有很好很全面的介绍:点击打开链接

具体怎么的使用也非常简单,直接上代码:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ApiModule } from '../api/api';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/retry';
 
@Injectable()
 
 // 登录的方法
 public LoginSubject = new Subject<any>();
 
 public getUserInfo(name, pwd):void {
 
  var data = {"username":name,"password":pwd};
  var val = this.HOST.host;
  this.$http
   .post(`${val}/login`, data)
   .retry(3)
   .subscribe( res => {
    this.LoginSubject.next(res)
   });
 }

subject是通过new的形式去创建的,那么当你服务端的数据返回之后,你可以使用next将相应流传递到你所定义的subject当中。服务层的写法就是这样,那么在组件中如何订阅呢?上代码:

this.service.getUserInfo(name, password)
this.subscript = this.service.LoginSubject.subscribe( data => { here is your code }

service需要在构造函数中去声明,这里就不写了。service中的getUserInfo方法接受两个参数,name与password,在这里进行发布操作,接下来就可以订阅了。由于有些时候,我们会希望在第二次订阅的时候,不会从头开始接收 Observable 发出的值,而是从第一次订阅当前正在处理的值开始发送,那么就需要对整个过程进行相应的处理。一般来说,我们不会主动去取消订阅,但是根据业务情况不同我们可能需要去取消订阅,怎么做呢?直接上代码:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { ApiModule } from '../api/api';
import { MyService } from '../myService/service.component';
import {NzMessageService} from 'ng-zorro-antd';
import { Subscription } from 'rxjs/Subscription';
 
@Component({
 selector: 'login-component',
 templateUrl: './login.component.html',
 styleUrls: [
  './login.component.less'
 ]
})
 
export class LoginComponent implements OnInit {
 subscript: Subscription
 
 constructor (private router:Router,
     private service: MyService,
     private _message: NzMessageService,) {
  this.subscript = new Subscription()
 }
  
 }
 
 ngOnInit ():void {
  this.service.getUserInfo(name, password)
  this.subscript = this.service.LoginSubject.subscribe( data => {
   // here is your code
  }
  
  this.subscript.unsubscribe()
 }
 
}

这就是从创建被观察者oberserver => 发布 => 订阅 => 取消订阅的整个流程。

拆分service服务

当你的业务越来越多的时候,你不可能只用一个service来支撑服务,你需要引入多个service进行与服务端的通讯。service模块化其实很简单,只要注意service进行provider的位置就行了,由于项目不同,具体的例子就不列举了。

章节四:打包发布

每次总是小手发抖,担心打包过程中会出现各种各样的问题。我就列举一下一些简单的常见的打包后可能会出现的问题,如果大家没遇到可以去程序员老黄历查查你今天可能适合打包提测,如果你遇到了那太好了,我就将这些坑分享给道友们。

(1)版本问题

由于整个项目是结合ng-zorro来做的,可能由于cli的版本问题,打包过后如果遇到了部门按钮失效,或者部门样式丢失的问题,那么你可以尝试去更新一下你全局的cli版本和项目中的cli版本,具体更新的方法,我在最前面已经写过了。

(2)服务端刷新路由丢失的问题(hash/histroy模式)

导入 HashLocationStrategy 及 HashLocationStrategy,开启hash模式。

import {HashLocationStrategy , LocationStrategy} from '@angular/common';
 
@NgModule({
 declarations: [AppCmp],
 bootstrap: [AppCmp],
 imports: [BrowserModule, routes],
 providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
});

再次打包就不会出现刷新后404的问题了。

(3) 服务端打开后无法加载的问题

如果你部署后,根本就打不开,可以检查一下你是否放在服务器根目录的文件中了,如果不是,你可以修改打包后文件中的index.html,找到 <base href="/" rel="external nofollow" >修改href为'./' 就OK啦

(4) 文件体积过大,优化问题。

你可以通过ng build --prod去开启细编译,他会将你用不到的模块和代码都删掉,--pord默认会开启-aot编译。

你还可以通过nginx gzip去进行优化操作,这里有一篇道友的文章,对优化进行了很多的处理,很牛,分享给大家:点击打开链接

结尾:

这是这次做angular 项目中遇到一些我个人比较印象深刻的问题,记录下来,也分享给大家。都是一些我自己理解的东西和百度学来的。可能会有错误,有些代码可能也只供大家参考用。也希望道友们能指出不足之处积极沟通

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
为jquery.ui.dialog 增加“在当前鼠标位置打开”的功能
Nov 24 Javascript
document.getElementById的简写方式(获取id对象的简略写法)
Sep 10 Javascript
jQuery中:not选择器用法实例
Dec 30 Javascript
JavaScript将Web页面内容导出到Word及Excel的方法
Feb 13 Javascript
AngularJS中的DOM操作用法分析
Nov 04 Javascript
JS实现的验证身份证及获取地区功能示例
Jan 16 Javascript
利用node.js如何搭建一个简易的即时响应服务器
May 28 Javascript
JavaScript中的FileReader图片预览上传功能实现代码
Jul 24 Javascript
Vue组件化开发思考
Feb 02 Javascript
Angular使用cli生成自定义文件、组件的方法
Sep 04 Javascript
JS实现计算小于非负数n的素数的数量算法示例
Feb 26 Javascript
node(koa2) web应用模块介绍详解
Mar 29 Javascript
JS实现简单的点赞与踩功能示例
Dec 05 #Javascript
node.js实现为PDF添加水印的示例代码
Dec 05 #Javascript
vue组件之间通信实例总结(点赞功能)
Dec 05 #Javascript
JS获取今天是本月第几周、本月共几周、本月有多少天、是今年的第几周、是今年的第几天的示例代码
Dec 05 #Javascript
JS获取月的第几周和年的第几周实例代码
Dec 05 #Javascript
JavaScript实现学生在线做题计时器功能
Dec 05 #Javascript
vue-cli3搭建项目的详细步骤
Dec 05 #Javascript
You might like
PHP自动更新新闻DIY
2006/10/09 PHP
PHP关联链接常用代码
2012/11/05 PHP
php下载excel无法打开的解决方法
2013/12/24 PHP
php调整服务器时间的方法
2015/04/03 PHP
php实现小程序支付完整版
2018/10/09 PHP
laravel http 自定义公共验证和响应的方法
2019/09/29 PHP
JavaScript插件化开发教程 (三)
2015/01/27 Javascript
js带点自动图片轮播幻灯片特效代码分享
2015/09/07 Javascript
AngularJS ng-bind-html 指令详解及实例代码
2016/07/30 Javascript
WebView启动支付宝客户端支付失败的问题小结
2017/01/11 Javascript
jquery仿苹果的时间/日期选择效果
2017/03/08 Javascript
jquery+ajax实现省市区三级联动 (封装和不封装两种方式)
2017/05/15 jQuery
Angular X中使用ngrx的方法详解(附源码)
2017/07/10 Javascript
Node.js实现注册邮箱激活功能的方法示例
2018/03/23 Javascript
微信小程序自定义toast组件的方法详解【含动画】
2019/05/11 Javascript
前后端常见的几种鉴权方式(小结)
2019/08/04 Javascript
Electron 调用命令行(cmd)
2019/09/23 Javascript
vue页面跳转实现页面缓存操作
2020/07/22 Javascript
JavaScript 监听组合按键思路及代码实现
2020/07/28 Javascript
vue + el-form 实现的多层循环表单验证
2020/11/25 Vue.js
简单实现python画圆功能
2018/01/25 Python
关于Python的一些学习总结
2018/05/25 Python
Python使用sorted对字典的key或value排序
2018/11/15 Python
python3实现猜数字游戏
2020/12/07 Python
Python openpyxl读取单元格字体颜色过程解析
2019/09/03 Python
python实现扫雷小游戏
2020/04/24 Python
使用SQLAlchemy操作数据库表过程解析
2020/06/10 Python
越南母婴用品购物网站:Kids Plaza
2020/04/09 全球购物
资深生产主管自我评价
2013/09/22 职场文书
企业新年寄语
2014/04/04 职场文书
爱我中华教学反思
2014/04/28 职场文书
创先争优标语
2014/06/27 职场文书
大学生撤销处分思想汇报
2014/09/12 职场文书
小学运动会报道稿
2014/10/04 职场文书
信访维稳承诺书
2015/05/04 职场文书
品牌形象定位,全面分析
2019/07/23 职场文书