详解关于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的$.event.fix函数统一浏览器event事件处理
Dec 21 Javascript
IE6下CSS图片缓存问题解决方法
Dec 09 Javascript
分享一个asp.net pager分页控件
Jan 04 Javascript
JS动态获取当前时间,并写到特定的区域
May 03 Javascript
非常实用的12个jquery代码片段
Nov 02 Javascript
jquery实现二级导航下拉菜单效果
Dec 18 Javascript
jquery form表单获取内容以及绑定数据
Feb 24 Javascript
JavaScript实现Base64编码转换
Apr 23 Javascript
jQuery多个版本和其他js库冲突的解决方法
Aug 11 Javascript
详谈angularjs中路由页面强制更新的问题
Apr 24 Javascript
jQuery 常用特效实例小结【显示与隐藏、淡入淡出、滑动、动画等】
May 19 jQuery
关于JS中的作用域中的问题思考分享
Apr 06 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中的加密功能
2006/10/09 PHP
mysql 中InnoDB和MyISAM的区别分析小结
2008/04/15 PHP
php _autoload自动加载类与机制分析
2012/02/10 PHP
PHP中file_exists与is_file,is_dir的区别介绍
2012/09/12 PHP
PHP设计模式(五)适配器模式Adapter实例详解【结构型】
2020/05/02 PHP
javascript 控制 html元素 显示/隐藏实现代码
2009/09/01 Javascript
基于prototype扩展的JavaScript常用函数库
2010/11/30 Javascript
js预载入和JavaScript Image()对象使用介绍
2011/08/28 Javascript
javascript利用控件对windows的操作实现原理与应用
2012/12/23 Javascript
jQuery晃动层特效实现方法
2015/03/09 Javascript
JavaScript 基础函数_深入剖析变量和作用域
2016/05/18 Javascript
基于jQuery ligerUI实现分页样式
2016/09/18 Javascript
微信JS-SDK自定义分享功能实例详解【分享给朋友/分享到朋友圈】
2016/11/25 Javascript
微信公众号  提示:Unauthorized API function 问题解决方法
2016/12/05 Javascript
JS+jQuery实现注册信息的验证功能
2017/09/26 jQuery
详解使用vuex进行菜单管理
2017/12/21 Javascript
讲解vue-router之什么是动态路由
2018/05/28 Javascript
vue-router懒加载速度缓慢问题及解决方法
2018/11/25 Javascript
TypeScript之调用栈的实现
2019/12/31 Javascript
python调用Delphi写的Dll代码示例
2017/12/05 Python
Python 中字符串拼接的多种方法
2018/07/30 Python
Django ORM 自定义 char 类型字段解析
2019/08/09 Python
在 Jupyter 中重新导入特定的 Python 文件(场景分析)
2019/10/27 Python
关于Pytorch的MNIST数据集的预处理详解
2020/01/10 Python
python爬虫开发之urllib模块详细使用方法与实例全解
2020/03/09 Python
在keras里面实现计算f1-score的代码
2020/06/15 Python
Draper James官网:知名演员瑞茜·威瑟斯彭所创品牌
2017/10/25 全球购物
美国山地自行车、露营、户外装备和服装购物网站:Aventuron
2018/05/05 全球购物
String这个类型的class为何定义成final?
2012/11/13 面试题
写出程序把一个链表中的接点顺序倒排
2014/04/28 面试题
竞选部门副经理的自荐书范文
2014/02/11 职场文书
面试必备的求职信
2014/05/25 职场文书
2014年物业管理工作总结
2014/11/21 职场文书
5.12护士节活动总结
2015/02/10 职场文书
事业单位财务人员岗位职责
2015/04/14 职场文书
JavaScript 实现页面滚动动画
2021/04/24 Javascript