مقالات طراحی سایت

آموزش موارد مربوط به طراحی و ساخت وب سایت

ممکن است بخواهید اسپینرلودینگی در پروژه داشته باشید که بدون اینکه نیاز باشد بصورت دستی شروع و پایان آنرا مشخص کنید نمایش داده شود.

یکی از ابزارهایی که میتوانید با کمک آن چنین لودینگی ایجاد کنید Http Interceptor است. Interceptor به شما این امکان را میدهد تا HttpRequest را قبل از ارسال به سرور و بعد از پاسخ سرور رهگیری کنید.

در این مقاله میخواهم اسپینرلودینگی ایجاد کنم که در زمان شروع درخواست http آغاز و زمان اتمام درخواست پایان یابد. لودینگ باید انقدر هوشمند باشد تا چند درخواست http را همزمان هندل کند و زمانی که اولین درخواست ارسال شود آغاز، و زمانی که آخرین درخواست تمام شود پایان یابد. حال قرار است چگونه این لودینگ را پیاده سازی کنیم؟

برای شروع، سرویس لودینگ را مینویسیم:


import { Injectable } from '@angular/core';
import {BehaviorSubject} from 'rxjs'; @Injectable({
providedIn: 'root'
})
export class LoadingService {
loadingSub: BehaviorSubject = new BehaviorSubject(false);
/**
* Contains in-progress loading requests
*/
loadingMap: Map<string, boolean=""> = new Map<string, boolean="">(); constructor() { } /**
* Sets the loadingSub property value based on the following:
* - If loading is true, add the provided url to the loadingMap
* with a true value, set loadingSub value to true
* - If loading is false, remove the loadingMap entry and only
* when the map is empty will we set loadingSub to false
* This pattern ensures if there are multiple requests awaiting completion,
* we don't set loading to false before
* other requests have completed. At the moment, this function is only
* called from the @link{HttpRequestInterceptor}
* @param loading {boolean}
* @param url {string}
*/
setLoading(loading: boolean, url: string): void {
if (!url) {
throw new Error('The request URL must be provided to the LoadingService.setLoading function');
}
if (loading === true) {
this.loadingMap.set(url, loading);
this.loadingSub.next(true);
}else if (loading === false && this.loadingMap.has(url)) {
this.loadingMap.delete(url);
}
if (this.loadingMap.size === 0) {
this.loadingSub.next(false);
}
}
}

در فایل loading.service.ts دو پراپرتی و یک متد داریم که:

  • loadingSub – یک behavior subject برای گوش دادن به مقدار وضعیت یا state لودینگ اسپینر است. مقدار true اسپینر را نشان میدهد و مقدار false اسپینر را مخفی میکند.
  • loadingMap – این پراپرتی یک Map از نوع Map<string,Boolean> است. Key یک url است و value آن true یا false است.
  • setLoading – این متد دو پارامتر قبول میکند، loading state از جنس Boolean و یک url. اگر مقدار state برابر با true باشد، url به map افزوده شود و مقدار loadingSub آپدیت میشود. اگر state برابر با false باشد ورودی loadingMap با url از پیش تعریف شده (provided url) حذف میشود. اگر loadingMap خالی باشد، مقدار loadingSub برابر با false میشود.

سپس app component را تعریف میکنیم:

 
<!-- Loading spinner -->
<div *ngIf="loading" class="loading-container flex-content-center">
  <mat-progress-spinner
   color="primary"
   mode="indeterminate">
</div>
 
<!-- the application -->
<div class="site-container">
  <div class="site-header">
    <app-header></app-header>
  </div>
  <div class="site-content">
    <router-outlet></router-outlet>
  </div>
</div>
 

@import 'variables';
.loading-container {
display: flex;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0,0,0,0.7);
z-index: 5;
} .site-container {
font-size: 14px;
font-family: Roboto, "Helvetica Neue", sans-serif;
height: 100vh;
background: #f1f1f1;
overflow: hidden; .site-header {
height: 64px;
} .site-content {
height: calc(100vh - 65px);
padding: 0 15px;
overflow: auto;
}
}

import {Component, OnInit} from '@angular/core';
import {delay} from 'rxjs/operators';
import {LoadingService} from './layout/services/loading/loading.service'; @Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'angular-boilerplate';
loading: boolean = false; constructor(
private _loading: LoadingService
){ } ngOnInit() {
this.listenToLoading();
} /**
* Listen to the loadingSub property in the LoadingService class. This drives the
* display of the loading spinner.
*/
listenToLoading(): void {
this._loading.loadingSub
.pipe(delay(0)) // This prevents a ExpressionChangedAfterItHasBeenCheckedError for subsequent requests
.subscribe((loading) => {
this.loading = loading;
});
} }

این سه فایل را در پروژه اضافه کردم. اطلاعات مرتبط در این فایل های *ngIg=”loading” در app.component.html و listenToLoading در app.component.ts است. متد listenToLoading به پراپرتی loadingSub در loadingService سابسکرایب (subscribe) میشود و پراپرتی لودینگ کامپوننت را آپدیت میکند. این کار محتوای div اسپینر لودینگ را آپدیت میکند. اگر شما با ارور ExpressionChangedAfterItHasBeenCheckedError مواجه شدید میتوانید در subscription تاخیر (delay) ایجاد کنید تا این ارور را نبینید.

بخش آخر این پیاده سازی مربوط به خود interceptor است که واقعا ساده است.


import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor, HttpResponse
} from '@angular/common/http';
import { Observable } from 'rxjs';
import {catchError, map} from 'rxjs/operators'
import {LoadingService} from '../../layout/services/loading/loading.service'; /**
* This class is for intercepting http requests. When a request starts,
* we set the loadingSub property
* in the LoadingService to true. Once the request completes
* and we have a response, set the loadingSub
* property to false. If an error occurs while servicing the request,
* set the loadingSub property to false.
* @class {HttpRequestInterceptor}
*/
@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor { constructor(
private _loading: LoadingService
) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<httpevent<any>> {
this._loading.setLoading(true, request.url);
return next.handle(request)
.pipe(catchError((err) => {
this._loading.setLoading(false, request.url);
return err;
}))
.pipe(map<httpevent<any>, any>((evt: HttpEvent<any>) => {
if (evt instanceof HttpResponse) {
this._loading.setLoading(false, request.url);
}
return evt;
}));
}
}

بخش مهم این فایل متد intercept است. این متد در ابتدای هر درخواست http فراخوانی شود. ما متد LoadingService.setLoading را در اینجا فراخوانی میکنیم و به آن مقدار true میدهیم (برای فعال کردن اسپینر) و url را از ارگومان request میگیریم. سپس HttpHandler را با request object بازمیگردانیم. اما همانطور که ملاحظه میکنید چندین pipe دارد.

اولین pipe همه خطاها را میگیرد و اسپینر را false میکند و خطا را برمیگرداند.

دومین pipe بررسی میکند HttpEvent آیا HttpResponse هست و اگر بود، اسپینر را false میکند و event را برمیگرداند. اگر پاسخی از درخواستی که درخواست آن تکمیل شده است دریافت کنیم میتوانیم اسپینر را متوقف کنیم.

به نقل از: medium

نوشتن دیدگاه