هرچند ارتقاء به HttpClient الزامی نیست و کدهای پیشین، هنوز هم به خوبی کار میکنند؛ اما طراحی جدید آن شامل ویژگیهای توکاری است که به سختی توسط HTTP Module پیشین قابل پیاده سازی هستند.
به روز رسانی وابستگیهای پروژه
پیش از هرکاری نیاز است وابستگیهای پروژه را به روز رسانی کرد و یکی از روشهای سادهی یافتن شماره نگارشهای جدید بستههای تعریف شدهی در فایل package.json برنامه، استفاده از بستهی npm-check-updates است:
npm install npm-check-updates -g ncu
در اینجا شماره نگارشهای جدید مشخص شدهاند و همچنین روش سریع ارتقاء به آنها نیز ذکر شدهاست. فقط کافی است دستورات ذیل را صادر کنیم تا این به روز رسانیها توسط ncu انجام شوند:
ncu -a npm update
تغییرات مورد نیاز جهت معرفی ماژول HttpClient
این ماژول جدید از طریق اینترفیس HttpClientModule ارائه میشود. بنابراین اولین تغییر در جهت ارتقاء به نگارش 4.3، اصلاح importهای لازم است:
از:
import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
پس از آن، این HttpClientModule را به لیست imports ماژول اصلی برنامه اضافه میکنیم؛ تا در کل برنامه قابل دسترسی شود:
@NgModule({ imports: [ // ... HttpClientModule, // ... ], declarations: [ ... ], providers: [ ... ], exports: [ ... ] }) export class AppModule { }
تغییرات مورد نیاز در سازندهها و تزریق وابستگیها
پس از تغییرات فوق، دیگر دسترسی به HttpModule پیشین را نداریم. بنابراین نیاز است هر جائی را که سرویس Http به سازندهی کلاسی تزریق شدهاست، یافته و به صورت ذیل تغییر دهیم:
از:
constructor(private http: Http) { }
import { HttpClient } from '@angular/common/http'; // ... constructor(private http: HttpClient) { }
تغییرات مورد نیاز در کدهای سرویسها جهت کار با HTTP Verbs
یکی از اهداف HTTP Client جدید، سادگی کار با اطلاعات دریافتی از سرور است. برای مثال در HTTP Module پیشین، روش دریافت اطلاعات از سرور به صورت ذیل است:
public get(): Observable<MyType> => { return this.http.get(url) .map((response: Response) => <MyType>response.json()); }
در HTTP Client جدید دیگر نیازی نیست تا متد ()json. فراخوانی شود. در اینجا به صورت پیشفرض نوع بازگشتی از سرور JSON فرض میشود. همچنین اکنون متدهای get/put/post و امثال آن برخلاف HTTP Client قبلی، جنریک هستند. یعنی در همینجا میتوان نوع بازگشتی را هم مشخص کرد. به این ترتیب، قطعه کد قدیمی فوق، به کد سادهی ذیل تبدیل میشود که در آن خبری از map و همچنین یک cast اضافی نیست:
get<T>(url: string): Observable<T> { return this.http.get<T>(url); }
post<T>(url: string, body: string): Observable<T> { return this.http.post<T>(url, body); }
نکته 1: در اینجا اگر خروجی از سرور، نوع دیگری را داشت، نیاز است responseType را به صورت صریحی به شکل ذیل مشخص کرد:
getData() { this.http.get(this.url, { responseType: 'text' }).subscribe(res => { this.data = res; }); }
نکته 2: ممکن است اطلاعات بازگشتی از سمت سرور، داخل یک فیلد محصور شده باشند:
{ "results": [ "Item 1", "Item 2", ] }
this.http.get('/api/items').subscribe(data => { this.results = data['results']; });
نکاتی را که باید حین کار با یک RxJS Observable-based API در نظر داشت
این API جدید نیز همانند قبل مبتنی بر RxJS Observables است. بنابراین نکات ذیل در مورد آن نیز صادق است:
- اگر متد subscribe بر روی این observables فراخوانی نشود، اتفاقی رخ نخواهد داد.
- اگر چندین بار مشترک این observables شویم، چندین درخواست HTTP صادر میشوند.
- این نوع خاص از observables، تنها یک مقدار را بازگشت میدهند. اگر درخواست HTTP موفقیت آمیز باشد، این observables یک نتیجه را بازگشت داده و سپس خاتمه پیدا میکنند.
- این observables اگر در حین درخواست HTTP با خطایی مواجه شوند، سبب صدور استثنایی میشوند.
تغییرات مورد نیاز در کدهای سرویسها جهت کار با HTTP Headers
در اینجا برای تعریف headers میتوان به صورت ذیل عمل کرد:
import { HttpHeaders } from "@angular/common/http"; const headers = new HttpHeaders({ "Content-Type": "application/json" });
const headers = new HttpHeaders().set("Accept", "application/json").set('Content-Type', 'application/json');
سپس آنرا به عنوان پارامتر سوم، به متدهای http ارسال میکنیم. یک مثال:
updateAppProduct(id: number, item: AppProduct): Observable<AppProduct> { const header = new HttpHeaders({ "Content-Type": "application/json" }); return this.http .put<AppProduct>( `${this.baseUrl}/UpdateProduct/${id}`, JSON.stringify(item), { headers: header } ) .map(response => response || {}); }
تعریف پارامتر options اینبار به صورت یک شیء دارای چندین خاصیت درآمدهاست. به همین جهت است که در اینجا یک {} را نیز مشاهده میکنید:
(method) HttpClient.post(url: string, body: any, options?: { headers?: HttpHeaders; observe?: "body"; params?: HttpParams; reportProgress?: boolean; responseType?: "json"; withCredentials?: boolean; }): Observable<Object>
یک نکته: شیء HttpHeaders به صورت immutable طراحی شدهاست. یعنی اگر آنرا به صورت ذیل فراخوانی کنیم:
const headers = new HttpHeaders(); headers = headers.set('Content-Type', 'application/json'); headers = headers.set('Accept', 'application/json');
const headers = new HttpHeaders() .set('Content-Type', 'application/json') .set('Accept', 'application/json') ;
امکان تعریف HttpParams
اگر به شیء options در تعریف فوق دقت کنید، دارای خاصیت اختیاری params نیز هست. از آن میتوان جهت تعریف کوئری استرینگها استفاده کرد. برای مثال درخواست ذیل:
http .post('/api/items/add', body, { params: new HttpParams().set('id', '3'), }) .subscribe();
/api/items/add?id=3
یک نکته: شیء HttpParams به صورت immutable طراحی شدهاست. یعنی اگر آنرا به صورت ذیل فراخوانی کنیم:
const params = new HttpParams(); params.set('orderBy', '"$key"') params.set('limitToFirst', "1");
const params = new HttpParams() .set('orderBy', '"$key"') .set('limitToFirst', "1");
const params = new HttpParams({fromString: 'orderBy="$key"&limitToFirst=1'});
تغییرات مورد نیاز در کدهای سرویسها جهت مدیریت خطاها
در اینجا اینبار خطای بازگشتی، از نوع ویژهی HttpErrorResponse است که شامل اطلاعات شماره کد و متن خطای حاصل میباشد:
import { HttpClient, HttpHeaders, HttpErrorResponse } from "@angular/common/http"; postData() { this.http.post(this.url, this.payload).subscribe( res => { console.log(res); }, (err: HttpErrorResponse) => { console.log(err.error); console.log(err.name); console.log(err.message); console.log(err.status); if (err.error instanceof Error) { console.log("Client-side error occured."); } else { console.log("Server-side error occured."); } } ); }
امکان سعی مجدد در اتصال توسط HTTP Client
ممکن است در اولین سعی در اتصال به سرور، خطایی رخ دهد و یا سرور در دسترس نباشد. در اینجا توسط متد retry میتوان درخواست سعی مجدد در اتصال را صادر کرد.
برای این منظور ابتدا عملگر retry مربوط به RxJS را import میکنیم:
import 'rxjs/add/operator/retry';
http .get<ItemsResponse>('/api/items') .retry(3) .subscribe(...);
امکان درخواست کل Response بجای Body
اگر به امضای پارامتر اختیاری options دقت کنید، خاصیت observe آن به صورت پیش فرض به body تنظیم شدهاست. به این معنا که تنها body یک response را تبدیل به یک شیء JSON میکند:
(method) HttpClient.post(url: string, body: any, options?: { headers?: HttpHeaders; observe?: "body"; params?: HttpParams; reportProgress?: boolean; responseType?: "json"; withCredentials?: boolean; }): Observable<Object>
http .get<MyJsonData>('/data.json', {observe: 'response'}) .subscribe(resp => { console.log(resp.headers.get('X-Custom-Header')); console.log(resp.body.someField); });
یک نکتهی تکمیلی: کدهای سری کار با فرمها در Angular را اگر به HttpClient ارتقاء دهیم، خلاصهی تغییرات آنها به این صورت خواهند بود.