浅谈Angular网络请求

在Angular网络请求是一个最多见的应用之一,下列我将以 ng-alain 项目为基础描述 Angular 网络请求。html

注:示例中代码都以简化的形式出现。

写在前面

Angular发起一个请求再简单不过即便用 HttpClient 类的各类方法,然在开始以前咱们应退一小步,先从如何构建一个 Restful API 开始,后端的API设计将很大程度决定先后端如何更优雅的开发有着很是大的关键性做用。前端

1、RESTful API 设计

私觉得API的设计分为请求与输出两个部分。而链接两者是依靠URL,关于URL如何更合理的设计能够参考阮一峰-RESTful API 设计指南git

这一部分要谈另外一个可能你们容易忽略的细节,请求体与返回体规范。这一点淘宝开放平台是一个很是好的典范,例如全部异常返回体:github

{
    "sub_msg":"非法参数",
    "code":50,
    "sub_code":"isv.invalid-parameter",
    "msg":"Remote service error"
}

全部这些规则能够由内部自行决议,再好比咱们中后台常用的是一种方式,全部返回体无论成功与否都包含如下对象:json

{
    "msg": "ok",
    "data": null
}

msg 来判断 ok 值表示成功,对于其余值表示容许直接显示给用户错误文本异常文本。后端

对于提交 POST 请求体的数据格式(content-type)主要两种比较常见:表单格式和JSON格式,两者也可能根据不一样场景状况使用特别是文件上传动做;固然对于大部分场景而言 JSON 格式最优先的形式,无论你是使用 Angular 表单的HTML模板或响应式驱动表单都是直接跟JSON打交道。api

2、请求流程

在 ng-alain 中,一个完整的 Angular 应用从前端 UI 交互到服务端处理流程是这样的:restful

一、首次启动 Angular 执行 APP_INITIALIZER
二、UI 组件交互操做;
三、使用 HttpClient 发送请求;
四、触发用户认证拦截器 @delon/auth,统一加入 token 参数;网络

a、若未存在 `token` 或已过时中断后续请求,直接跳转至登陆页;

五、触发默认拦截器,统一处理前缀等信息;
六、获取服务端返回;
七、触发默认拦截器,统一处理请求异常、业务异常等;
八、数据更新,并刷新 UI。app

本文咱们不介绍渲染方面,所以 2,6,8 三点将不作介绍。

一、APP_INITIALIZER

应用初始化是在应用启动过程当中有且只执行一次,通常来说咱们须要在应用一启动时加载一些数据:应用信息、通用数据字典、用户数据等。

只须要向 APP_INITIALIZER 注册一个带有 Promise 返回值便可;例如:

{
  provide: APP_INITIALIZER,
  useValue: () => new Promise(() => {}),
  multi: true
}

正由于是一个 Promise 异步,咱们就能够在这里利用 HttpClient 作网络请求,从而实如今 Angular 启动以前经过网络请求获取一个启用后一开始就须要的数据。

注:固然在这里发起的网络请求拦截器依然有效,若拦截器包含一些用户 Token 的有效性校验而致使跳转至登陆页时,可能要当心处理了。

但无论如何最终你想启动 Angular 都必须确保 Promise 正确的调用 resolve()

二、HttpClient

HttpClient 是 Angular 封装了一个简化的 API 来实现 HTTP 客户端功能,例如一个 get 请求:

constructor(http: HttpClient) {
  http.get('/user/1').subscribe((user) => {
    console.log(user);
  });
}

另外一个 post 请求:

constructor(http: HttpClient) {
  http.post('/user/1', { a: 1 }).subscribe((user) => {
    console.log(user);
  });
}

全部请求类型返回的结果都是 Observable<any> 类型,意味着无论若是你都必须调用 subscribe 才会真正的发起请求。大多数状况下你可能会以为很麻烦,但当你须要一些节流或数据转换时就显得 rxjs 的魅力,有关更多细节自行Google rxjs

三、拦截器

拦截网络请求或响应,用于统一处理请求或响应结果数据。而且能够运用多个拦截器且按顺序执行,相似于 Node 中间件。

一个简单示例

只须要简单实现 HttpInterceptor 接口便可:

export class SimpleInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
    const newReq = req.clone();
    return next.handle(newReq).pipe()
  }
}

拦截器返回的结果是一个 Observable 值,这意味着同一个拦截器代码包含着请求和响应两个部分的处理,全部在 Angular 拦截器里并无明确区分请求和响应处理,这也是 rxjs 的魅力。

使用 req.clone() 克隆一些新的请求体,固然请求体包含着全部 HttpClient 发起数据及参数。例如给全部请求体的 headers 加入用户 Token 值。

const newReq = req.clone({
  setHeaders: { Authorization: `Bearer ${this.token}` },
});

当响应体网络状态码非 401 时,打算跳转至登陆页,则:

return next.handle(newReq)
           .pipe(
             catchError(err => {
               if (err.status === 401) {
                 this.injector.get(Router).navigateByUrl('/login');
               }
             })
           )

最后,在模块里注册,若你但愿在整个应用有效能够在根模块里注册:

{ provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true },

拦截器顺序

拦截器能够注册在任何模块里,而一个网络请求所通过拦截器从模块向上查找至根模块,若一个模块包含多个拦截器时按代码顺序执行。

3、ng-alain 请求处理

ng-alain 默认装载了两个拦截器:@delon/auth 用户认证和默认拦截器。

一、用户认证

自己是为 ng-alain 脚手架提供的一个 用户认证模块,包含主流的 JWT(Json Web Token)和一个相对通用 Simple Web Token,而其核心是对认证过程进一步处理。而一般其核心在于用户 Token 的获取、使用环节。

同时,@delon/auth 并不会关心用户界面是怎么样,只须要当登陆成功后将后端返回的数据交给 ITokenService,它会帮你存储在 localStorage(默认) 当中;当发起一个网络请求时,它会在自动在 header(默认) 当中加入相应的 token 信息。

所以,@delon/auth 不限于 ng-alain 脚手架,任何 Angular 项目均可以使用它。

默认装载了 SimpleInterceptor 拦截器,意味者一开始使用 ng-alain 为何会平白无故没法正确请求,而是直接抛出异常。

ng-alain 是一个完整且可直接运用项目的脚手架,所以全部默认配置都尽量生产环境中代码,其实理解这一点很重要,由于大部分一开始总但愿使用一个 Hello World 请求来决定是否是真的可使用。

有关更多细节请参考文档

二、默认拦截器

DefaultInterceptor 拦截器,它是一个默认拦截器示例代码,包含请求体和响应体的处理。

例如当咱们统一响应体以下:

{
    "msg": "ok",
    "data": { id: 1, name: "cipchk" }
}

对于 subscribe 结果来讲只须要关心 data 部分,所以能够在拦截器进一步转化:

return of(new HttpResponse(Object.assign(event, { body: body.data })));

使在订阅结果时给保持一个最简单有效数据:

http.get('/user/1').subscribe(user => console.log(user));
// output: { id: 1, name: "cipchk" }

更多作法,例如:统一处理异常消息等,能够参考 default.interceptor.ts 的写法。

总结

Angular 网络请求看起来就像一个简化版的 Web 服务,发起的请求通过一道道关卡后,接收响应结果时又通过原先通过的一道道关卡最后交给用户。

固然这一切的本质仍是 rxjs 带来的。曾经有人提过为何 ng-alain 不采用 Redux 形式,但我实在找不到有什么理由要这么作,大部分中后台都以网络请求来完成大部分事务,而 Angular 网络请求又那么清晰。

(完)