阅读 Angular 6/RxJS 最新教程,请访问
前端修仙之路
Angular 4.3.0-rc.0 版本已经发布?。在这个版本中,我们等到了一个令人兴奋的新功能 – HTTPClient API 的改进版本,以后妈妈再也不用担心我处理 HTTP 请求了?。
HttpClient 是已有 Angular HTTP API 的演进,它在一个单独的
@angular/common/http
包中。这是为了确保现有的代码库可以缓慢迁移到新的 API。
接下来让我们开启 Angular 新版 Http Client 之旅。
安装
首先,我们需要更新所有的包到 4.3.0-rc.0
版本。然后,我们需要在 AppModule
中导入 HttpClientModule
模块。具体如下:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
现在一切准备就绪。让我们来体验一下我们一直期待的三个新特性。
特性一 默认 JSON 解析
现在 JSON 是默认的数据格式,我们不需要再进行显式的解析。即我们不需要再使用以下代码:
http.get(url).map(res => res.json()).subscribe(...)
现在我们可以这样写:
http.get(url).subscribe(...)
特性二 支持拦截器 (Interceptors)
拦截器允许我们将中间件逻辑插入管线中。
请求拦截器 (Request Interceptor)
import {
HttpRequest,
HttpHandler,
HttpEvent
} from '@angular/common/http';
@Injectable()
class JWTInterceptor implements HttpInterceptor {
constructor(private userService: UserService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const JWT = `Bearer ${this.userService.getToken()}`;
req = req.clone({
setHeaders: {
Authorization: JWT
}
});
return next.handle(req);
}
}
如果我们想要注册新的拦截器 (interceptor),我们需要实现 HttpInterceptor
接口,然后实现该接口中的 intercept
方法。
export interface HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>;
}
需要注意的是,请求对象和响应对象必须是不可修改的 (immutable)。因此,我们在返回请求对象前,我们需要克隆原始的请求对象。
next.handle(req)
方法使用新的请求对象,调用底层的 XHR 对象,并返回响应事件流。
响应拦截器 (Response Interceptor)
@Injectable()
class JWTInterceptor implements HttpInterceptor {
constructor(private router: Router) {}
intercept(req: HttpRequest < any > ,
next: HttpHandler): Observable < HttpEvent < any >> {
return next.handle(req).map(event => {
if (event instanceof HttpResponse) {
if (event.status === 401) {
// JWT expired, go to login
}
}
return event;
}
}
}
响应拦截器可以通过在 next.handle(req)
返回的流对象 (即 Observable 对象) 上应用附加的 Rx 操作符来转换响应事件流对象。
接下来要应用 JWTInterceptor
响应拦截器的最后一件事是注册该拦截器,即使用 HTTP_INTERCEPTORS
作为 token,注册 multi Provider:
[{ provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true }]
特性三 进度事件 (Progress Events)
进度事件可以用于跟踪文件上传和下载。
import {
HttpEventType,
HttpClient,
HttpRequest
} from '@angular/common/http';
http.request(new HttpRequest(
'POST',
URL,
body,
{
reportProgress: true
})).subscribe(event => {
if (event.type === HttpEventType.DownloadProgress) {
// {
// loaded:11, // Number of bytes uploaded or downloaded.
// total :11 // Total number of bytes to upload or download
// }
}
if (event.type === HttpEventType.UploadProgress) {
// {
// loaded:11, // Number of bytes uploaded or downloaded.
// total :11 // Total number of bytes to upload or download
// }
}
if (event.type === HttpEventType.Response) {
console.log(event.body);
}
})
如果我们想要跟踪文件上传或下载的进度,在创建请求对象时,我们需要配置 {reportProgress: true}
参数。
此外在回调函数中,我们通过 event.type
来判断不同的事件类型,从进行相应的事件处理。
HttpEventType
枚举定义如下:
export enum HttpEventType {
/**
* 表示请求已经被发送
*/
Sent,
/**
* 已接收到上传进度事件
*/
UploadProgress,
/**
* 已接收到响应状态码和响应头
*/
ResponseHeader,
/**
* 已接收到下载进度事件
*/
DownloadProgress,
/**
* 已接收全部响应,包含响应体
*/
Response,
/**
* 用户自定义事件,来自拦截器或后端
*/
User,
}
其实除了上面介绍三个新的功能之外,还有以下两个新的功能:
- 基于 Angular 内部测试框架的
Post-request verification
和flush
功能 - 类型化,同步响应体访问,包括对 JSON 响应体类型的支持。
最后我们来通过 client_spec.ts 文件中的测试用例,来进一步感受一下上述的新特性。
其它特性
发送 GET 请求
describe('HttpClient', () => {
let client: HttpClient = null !;
let backend: HttpClientTestingBackend = null !;
beforeEach(() => {
backend = new HttpClientTestingBackend();
client = new HttpClient(backend);
});
afterEach(() => { backend.verify(); }); // 请求验证
describe('makes a basic request', () => {
it('for JSON data', (done: DoneFn) => {
client.get('/test').subscribe(res => {
expect((res as any)['data']).toEqual('hello world');
done();
});
backend.expectOne('/test').flush({'data': 'hello world'});
});
it('for an arraybuffer', (done: DoneFn) => {
const body = new ArrayBuffer(4);
// 还支持 {responseType: 'text'}、{responseType: 'blob'}
client.get('/test', {responseType: 'arraybuffer'}).subscribe(res => {
expect(res).toBe(body);
done();
});
backend.expectOne('/test').flush(body);
});
it('that returns a response', (done: DoneFn) => {
const body = {'data': 'hello world'};
client.get('/test', {observe: 'response'}).subscribe(res => {
expect(res instanceof HttpResponse).toBe(true);
expect(res.body).toBe(body);
done();
});
backend.expectOne('/test').flush(body);
});
});
});
发送 POST 请求
describe('makes a POST request', () => {
it('with text data', (done: DoneFn) => {
client.post('/test', 'text body', {observe: 'response', responseType: 'text'})
.subscribe(res => {
expect(res.ok).toBeTruthy();
expect(res.status).toBe(200);
done();
});
backend.expectOne('/test').flush('hello world');
});
it('with json data', (done: DoneFn) => {
const body = {data: 'json body'};
client.post('/test', body, {observe: 'response',
responseType: 'text'}).subscribe(res => {
expect(res.ok).toBeTruthy();
expect(res.status).toBe(200);
done();
});
const testReq = backend.expectOne('/test');
expect(testReq.request.body).toBe(body);
testReq.flush('hello world');
});
});
发送 JSONP 请求
describe('makes a JSONP request', () => {
it('with properly set method and callback', (done: DoneFn) => {
client.jsonp('/test', 'myCallback').subscribe(() => done());
backend.expectOne({method: 'JSONP', url: '/test?myCallback=JSONP_CALLBACK'})
.flush('hello world');
});
});