在angular中,服务用于书写可重用的公共功能(如日志处理、权限管理等)和复杂的业务逻辑。
angular服务
angular服务一般是封装了某种特定功能的独立模块,它可以通过注入的方式供外部调用。服务的使用场景:
- 多个组件中出现重复代码时,把重复代码提取到服务中实现代码复用。
- 当组件中掺杂了大量的业务代码和数据处理逻辑时,把这些逻辑封装成服务供组件使用,组件只负责UI相关的逻辑,有利于后续的更新和维护。
- 把需要共享的数据存储在服务中,通过在多个组件中注入同一个服务实例实现数据共享。
业务逻辑封装
虽然可以把所有的代码都写在组件里,但这样会使组件的代码量非常大而且显得杂乱不堪,不利于后续代码的维护。
//contact.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class ContactService{
//从服务器上获取联系人信息
getContactsData(){
//...
}
//更新联系人信息到服务器
updataeContacts(contact:Contact){
//...
}
}
@Injectable()
装饰器用于说明被装饰的类依赖了其他服务,而这里ContactService
没有依赖其他服务,所以@Injectable()
可以省略,但推荐加上。
在组件中通过依赖注入使用ContactService
服务,需要先将ContactService
服务通过import
导入,再在组件的构造函数中引入服务的实例,接着就可以在逻辑代码中调用服务的方法了。
//edit.component.ts
import { Component,OnInit,Input } from '@angular/core';
import { ContactService } from 'shared/conctact.service';
@Component({
selector:'my-operate',
templateUrl:'app/edit/edit.component.html',
styleUrls:['app/edit/edit.component.css']
})
export class EditComponent implements OnInit {
constructor(
private _contactService:ContactService,
){}
}
共享服务示例
通过组件间共享同一服务实例可以实现组件间通信。
下面这个例子中的子组件接收用户输入并调用SharedService
的append()
方法添加数据,父组件则把SharedService
的数据变化实时展示到模板中。
//SharedService
import { Injectable } from '@angular/core';
@Injectable()
export class SharedService{
list:string[]=[];
append(str:string){
this.list.push(str);
}
}
//父组件
import { Component } from '@angular/core';
import { SharedService } from './share.service';
import { ChildComponent } from './child.component';
@Component({
selector:'parent-component',
template:`
<ul *ngFor="#item in list">
<li>{{item}}</li>
</ul>
<child-component></child-component>
`,
providers:[SharedService]
})
export class ParentComponent{
list:string[]=[];
constructor(private _sharedService:SharedService){}
ngOnInit():any{
this.list=this._sharedService.list;
}
}
为了让父组件和子组件能获取到SharedService
的同一个实例,需要在父组件中添加providers:[SharedService]
,子组件不需要添加,否则父组件和子组件获得的SharedService
将是两个不同实例。也可以在父组件和子组件所属的模块中统一配置providers:[SharedService]
,那么父组件就不需要配置了。
//子组件
import { Component } from '@angular/core';
import { SharedService } from './shared.service';
@Component({
selector:'child-component',
template:`
<input type="text" [(ngModel)]="inputText" />
<button (click)="add()"></button>
`
})
export class ChildComponent{
inputText:string='testing data';
constructor(private _sharedService:SharedService){}
add(){
this._sharedService.append(this.inputText);
this.inputText='';
}
}
HTTP服务
在angular应用中使用HTTP服务需要三步:
- 在模块装饰器
@NgModule
中导入HttpModule
- 在组件模块中导入HTTP服务
- 在组件的构造函数中声明引入
//app.module.ts
import { HttpModule } from '@angular/http';
@NgModule({
imports:[
HttpModule
]
bootstrap:[AppComponent]
})
export class AppModule {}
//contact.component.ts
import { Component } from '@angular/core';
import { bootstrap } from ' @angular/platform-browser/browser';
import { Http } from '@angular/http';
@Component({
selector:'contact',
template:`<div>hello</div>`
})
export class ContactComponent {
constructor(http:Http){
//...
}
}
引入HTTP服务后,组件就可以用AJAX和JSONP两种方式发送数据请求了。
AJAX介绍
AJAX是使用XMLHttpRequest
对象向服务器发送请求并处理响应的通讯技术。XMLHttpRequest
支持同步和异步方式发送请求,但通常都采用异步方式发送HTTP请求。
三种处理异步操作的方式:
- 使用回调函数
- 使用
Promise
- 使用
Observable
HTTP服务的API接口返回的是Observable
对象。Observable
是响应式编程模型Rx
的核心概念。RxJS是它的JS版本。angular对RxJS做了封装处理。
HTTP GET请求
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
@NgModule({
imports:[HttpModule],
declarations:[AppComponent],
bootstrap:[AppComponent]
})
export class AppModule {}
首先通过在@NgModule
装饰器中引入HttpModule
,后面编写组件时就不需要在providers
数组中引入了。
//contact.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';
const CONTACT_URL=`./app/contacts.json`;
@Injectable()
export class ContactService{
constructor(private _http:Http){}
getContacts():Observable<any[]>{
return this._http.get(CONTACT_URL)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res:Response){
let body=res.json();
return body.data||{};
}
private handleError(error:any){
let errMsg=(error.message)?error.message:error.status?`${error.sttatus}`-`${error.statusText}`:'Server error';
console.log(errMsg);
return Observable.throw(errMsg);
}
}
_http.get()
返回的是一个Observable
对象,而map()
方法是它的常用操作之一。在extractData()
方法里需要通过json()
方法把服务器返回的数据转换成JSON
对象。
在组件中使用ContactService
服务:
//contact.component.ts
import { Component } from '@angular/core';
import { ContactService } from 'shared/contact.service';
@Component({
//...
})
export class ContactComponent{
//...
constructor(private _contactService:ContactService){}
getContacts(){
return this._contactService.getContacts()
.subscribe(
contacts=>this.contacts=contacts,
error=>this.errorMessage=<any>error
);
}
}
HTTP POST请求
//list.component.ts
//...
addContact(contact:Contact){
//do something
}
因为要发送POST
请求,并且在请求体中传递JSON
格式的数据,所以要设置HTTP请求头Content-Type
的值为'application/json'
。
首先需要先导入Headers
和RequestOptions
对象。
//contact.service.ts
import { Headers,RequestOptions } from '@angular/http';
//...
addContact(contact:Contact):Observable<Contact> {
let body=JSON.stringify(contact);
let headers=new Headers({ 'Content-Type':'application/json' });
let options=new RequestOptions({headers:headers});
return this._http.post(CONTACT_URL,body,options)
.map(this.extractData)
.catch(this.handleError);
}
Headers
是RequestOptions
的一个属性,RequestOptions
作为第三个参数传递给HTTP服务的post()
方法,这样就可达到自定义请求头的目的。
即使Content-Type
已经被指定为JSON
类型,但是服务端仍然只接收字符串,所以发送请求前,先要用JSON.stringify()
方法处理下数据。
在组件中使用addContact()
方法:
//list.component.ts
import { ContactService } from 'shared/contact.service';
//...
addContact(contact:Contact) {
if(!contact) {return ;}
this._contactService.addContact(contact)
.subscribe(
contact=>this.contacts.push(contact),
error=>this.errorMessage=<any>error
);
}
在组件的addContact()
方法中订阅了ContactService
中addContact()
方法返回的Observable
实例,请求返回时就会把新联系人数据追加到contacts
数组,用于展示给用户。
另外,HTTP服务返回的Observable
对象可以方便地转换成Promise
对象。下面是ContactService
服务的Promise
版本。
import { Injectable } from '@angular/core';
import { Http,RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Rx';
const CONTACT_URL=`./app/contacts.json`;
@Injectable()
export class ContactService {
constructor(private _http:Http){
}
getContacts():Promise<any[]> {
return this._http.get(CONTACT_URL)
.toPromise()
.then(this.extractData)
.catch(this.handleError)
}
private extractData(res:Response) {
let body=res.json();
return body.data||{ };
}
private handleError(error:any) {
let errMsg=(error.message)?error.messange:
error.status?`${error.status} - ${error.statusText}`:'Server error';
console.error(errMsg);
return Promise.reject(errMsg);
}
}
JSONP
在Web开发中,有时候需要向与当前页面不同源的服务器发起AJAX请求,会发现该请求被浏览器阻止了,这就是浏览器同源策略的访问限制。所谓“源”就是URI
的协议(Scheme
)、主机名(Host
)和端口(Port
)这几部分的组合,当全部相同时才算是同源。
如果服务器和浏览器都支持CORS
协议,则AJAX不受同源策略的访问限制。
如果浏览器不支持或服务器不方便实施CORS
,则可以选择JSONP
,它适用于任何浏览器。JSONP
只能发起GET
请求。
HTTP服务中包含了JSONP
服务。
import { Injectable } from '@angular/core';
import { Jsonp,URLSearchParams } from '@angular/http';
@Injectable()
export class ContactService{
constructor(private _jsonp:Jsonp){}
getContacts(){
let URL='http://www.others.com/contacts';
let params=new URLSearchParams();
params.set('format','json');
params.set('callback','JSONP_CALLBACK');
return this._jsonp
.get(URL,{search:params})
.map(res=>res.json())
.subscribe(
contacts=>this.contacts=contacts,
error=>this.errorMessage=<any>error
)
}
}
HttpModule
HttpModule
是在@angular/http
中定义的用于封装HTTP相关功能的模块,它包含了HTTP服务,同时也包含了HTTP所依赖的其他服务,HttpModule
模块主要包含了以下服务。
- HTTP:封装了常用的HTTP请求方法
- BrowserXhr:用于创建
XMLHttpRequest
实例的工厂 - XHRBackend:用于创建一个
XHRConnection
实例,该实例会使用BrowserXhr
对象来处理请求 - XSRFStrategy:这是一个接口,它定义了配置
XSRF
攻击保护的方法,angular提供了CookieXSRFStrategy
这个类来帮助设置Request Header
用于防止XSRF
攻击 - RequestOptions:封装了HTTP请求参数,它有一个子类
BaseRequestOptions
,默认将请求设置为GET
方式 - ResponseOptions:封装了HTTP响应参数,它有一个子类
BaseResponseOptions
,默认将响应设置为成功
HttpInterceptor示例
在angular应用中,有时候需要对所有HTTP请求做统一的处理,如添加一些必要的HTTP自定义请求头域,或在后端返回某个错误时进行统一处理,或统一在请求发出前显示“加载中”的状态并在请求返回后关闭该状态等。这种情况下,就可以通过实现ConnectionBackend
类并重现createConnection()
方法来达到这一目的。
首先写一个HttpInterceptor
服务,对请求发送前后进行处理。
//http-interceptor.ts
import { Injectable } from '@angular/core';
import { Request,Response } from '@angular/http';
import { Observable } from 'rxjs';
@Injectable()
export class HttpInterceptor{
beforeRequest(request:Request):Request{
//请求发送前的处理逻辑
console.log(request);
return request;
}
afterResponse(res:Observable<Response>):Observable<any>{
//请求响应后的处理逻辑
res.subscribe((data)=>{
console.log(data);
});
return res;
}
}
接着实现ConnectionBackend
抽象类,目的是封装XHRBackend
服务,在XHRBackend
创建XHRConnection
实例前后进行相应的逻辑处理。
//http-interceptor-backend.ts
import { Injectable } from '@angular/core';
import { ConnectionBackend,XHRConnection,XHRBackend,Request }
from '@angular/http';
import { HttpInterceptor } from './http-interceptor';
@Injectable()
export class HttpInterceptorBackend implements ConnectionBackend{
constructor(private _httpInterceptor:HttpInterceptor,
private _xhrBackend:XHRBackend){}
createConnection(request:Request):XHRConnection{
let interceptor=this._httpInterceptor;
//在请求发出前,拦截请求并调用HttpInterceptor对象的beforeRequest()方法
//进行处理
let req=interceptor.beforeRequest?interceptor.beforeRequest(request):request;
//通过XHRBackend对象创建XHRConnection实例
let result=this._xhrBackend.createConnection(req);
//得到响应后,拦截并调用HttpInterceptor对象的afterResponse方法
//进行处理
result.response=interceptor.afterResponse?interceptor.afterResponse(result.response):result.response;
return result;
}
}
HTTP服务默认是使用XHRBackend
对象作为构造函数的第一个参数创建的。
export function httpFactory(xhrBackend:XHRBackend,requestOptions:RequestOptions):Http{
return new Http(xhrBackend,requestOptions);
}
为了使HttpInterceptorBackend
拦截生效,需要将创建HTTP服务时的第一个参数改为HttpInterceptorBackend
对象。
//http-factory.ts
import { RequestOptions,Http } from '@angular/http';
import { HttpInterceptorBackend } from './http-interceptor-backend';
export function httpFactory(httpInterceptorBackend:HttpInterceptorBackend,
requestOptions:RequestOptions
):Http{
return new Http(httpInterceptorBackend,requestOptions);
}
最后在根模块中导入以上定义的服务。
//app.module.ts
import { Http,RequestOptions } from '@angular/http';
import { HttpInterceptorBackend } from './interceptor/http-interceptor-backend';
import { HttpInterceptor } from './interceptor/http-interceptor';
import { httpFactory } from './interceptor/http-factory';
//...
providers:[
HttpInterceptorBackend,HttpInterceptor,
{provide:Http,useFactory:httpFactory,deps:[
HttpInterceptorBackend,RequestOptions
]}
]
当通过angular的HTTP服务发出任何一个HTTP请求时,在控制台中都能打印出Request
对象和Response
对象。