ng2服务

在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,
    ){}
}

共享服务示例

通过组件间共享同一服务实例可以实现组件间通信。
下面这个例子中的子组件接收用户输入并调用SharedServiceappend()方法添加数据,父组件则把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'
首先需要先导入HeadersRequestOptions对象。

//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);
}

HeadersRequestOptions的一个属性,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()方法中订阅了ContactServiceaddContact()方法返回的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对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,865评论 6 13
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,465评论 25 707
  • 2500年前中国古书《晏子春秋》里,有一个疑似现代“成功学”的故事。一个名叫梁丘的人佩服晏子的聪明和学识,对晏子说...
    悟三分阅读 5,932评论 0 67
  • 转自云村 “孤独”。这两个字拆开看,有小孩,有水果,有走兽,有蚊蝇,足以撑起一个盛夏傍晚的巷子口,人味十足。 夏日...
    坚决不穿秋裤阅读 516评论 0 0