1

i have an Ionic application, this application contains set of pages and services, now since i'm working with Ionic & Angular, i decided to build reusable code for different projects.

for example, Http service: in this service i want to centralize the code which manage the communication with server side to achieve the reusable concept and the change will be in one place.

my Http.service code is:

export interface RequestOptions {
  observable?: any;
  url?:string;
  method?: string;
  successCallBack?: any;
  notSuccessCallBack?: any;
  useDefaultNotSuccessCallBack?: boolean;
  errorCallBack?: any;
  useDefaultErrorCallBack?: boolean;
  completeCallBack? : any;
  sendToken?: boolean;
  data?: any;
  refresher?:Refresher;
  infinitScroller?: InfiniteScroll;
  loader?: Loading;
}

export interface ForkOptions {
  requests: RequestOptions[];
  useDefaultNotSuccessCallBack?: boolean;
  errorCallBack?: any;
  useDefaultErrorCallBack?: boolean;
  completeCallBack? : any;
  refresher?:Refresher;
  infinitScroller?: InfiniteScroll;
  loader?: Loading;
}

import {Injectable} from "@angular/core";
import {AuthenticationService} from "./authentication.service";
import {Observable} from "rxjs/Observable";
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {AlertController, InfiniteScroll, Loading, Refresher} from "ionic-angular";
import 'rxjs/add/observable/forkJoin';

@Injectable()
export class HttpConnectionService {

  constructor(
    private http: HttpClient,
    private authService: AuthenticationService,
    private alertCtrl: AlertController
  ) {}

  /**
   * @param {ForkOptions} options: an options which contains the list of requests to be forked together.
   * this method will construct an array of observables and handle the response using @responseHandler and @errorHandler methods
   * in this service
   */
  httpRequests( options: ForkOptions) {
    // build the array of observables
    let observables = [
      ...options.requests.map((request) =>  request.observable)
    ];
    // subscribe to these observables
    Observable.forkJoin(observables).subscribe(
      (results) => {
        // handle the response for each of the requests
        for(let i = 0; i < results.length; i++) {
          this.responseHandler(results[i], options.requests[i]);
        }
      }, (error) => {
        // handle the errors
        this.errorHandler(error, options);
      }, () => {
        // execute the complete handler
        this.completeHandler(options);
      }
    );
  }

  /**
   * @param {RequestOptions} requestOptions: contains the options and attributes of the request to be constructed
   * @returns {any} return a ready to subscribe observable
   */
  createObservable(requestOptions: RequestOptions) {
    // switch statement to handle the different types of methods.
    switch(requestOptions.method) {
      // if the case is post or delete method, they would have the same parameters.
      case 'post' : case 'delete' :
        return this.postDelete(requestOptions);
      // if the case is get method, it will be constructed differently
      case 'get':
        return this.get(requestOptions);
    }
  }

  /**
   *
   * @param {RequestOptions} requestOptions: the options and attribute of the request (post or delete)
   * @returns {any}: return the request observable.
   */
  private postDelete(requestOptions: RequestOptions) {
    return this.http[requestOptions.method](requestOptions.url, requestOptions.data);
  }

  /**
   *
   * @param {RequestOptions} requestOptions
   * @returns {Observable<Object>}
   */
  private get(requestOptions: RequestOptions) {
    return this.http.get(requestOptions.url);
  }

  /**
   *
   * @param {RequestOptions} requestOptions identify different attributes of the request.
   */
  httpRequest(requestOptions: RequestOptions) {
    // send the http request and use the method attribute

    // if there is observable sent with request
    let observable = requestOptions.observable;
    if(observable == undefined){
      // if there is no observable, create one
      observable = this.createObservable(requestOptions);
    }
    observable.subscribe(
      (response: any) => {
        // call the response handler
        this.responseHandler(response, requestOptions);
      }, (error) => {
        // call the error handler
        this.errorHandler(error, requestOptions);
      }, () => {
        // call the complete handler
        this.completeHandler(requestOptions);
      }
    );
  }

  private responseHandler(response, requestOptions: RequestOptions) {
    // if the response success, execute the success call back
    if(response.isSuccess) {
      requestOptions.successCallBack(response.result);
      // check if there is infinit scroller and the response is empty, then disable the infinit scroller
      if(requestOptions.infinitScroller && response.result.length == 0) {
        requestOptions.infinitScroller.enable(false);
      }
    }else {
      // if the response is not success, check if the notSuccessCallBack is defined,
      // if notSuccessCallBack is defined, execute it, other wise, execute the default notSuccess callBacl
      if(requestOptions.notSuccessCallBack) {
        // execute the provided not success callBack
        requestOptions.notSuccessCallBack(response);
      }else if(requestOptions.useDefaultNotSuccessCallBack){
        // execute the default not success callBack
        this.defaultNotSuccessResponse(response);
      }
    }
  }

  private errorHandler(error, requestOptions: RequestOptions  | ForkOptions) {
    // check for the provided error callBack.
    if(requestOptions.errorCallBack) {
      // execute the provided callBack
      requestOptions.errorCallBack(error);
    }else if(requestOptions.useDefaultErrorCallBack){
      // if u can default error handler
      this.defaultErrorHandler(error, requestOptions);
    }
  }

  /**
   *
   * @param {RequestOptions | ForkOptions} requestOptions: the requests options which contain completeCallBack.
   */
  private completeHandler(requestOptions: RequestOptions  | ForkOptions) {
    // if there is complete callBack, execute it.
    if(requestOptions.completeCallBack) {
      requestOptions.completeCallBack();
    }
    // turn off the external components after the response is arrived
    // for example: loader, infinit scroller, refreshing
    this.turnOffExternalComponents(requestOptions);
  }

  /**
   * contains the default behavioral for the not success response,
   * it can be terminated if @useNotSuccessResponse = false in the request options.
   * @param response: the response from the server side which contains the error message.
   */
  private defaultNotSuccessResponse(response) {
    // the default behavioral is to display an error message.
    this.alertCtrl.create({
      title: "Error!",
      subTitle: response.message
    }).present();
  }

  /**
   *
   * @param error: the error object
   * @param {RequestOptions} requestOptions: contains attributes about the request
   * used as params to access the external components when turning them off.
   */
  private defaultErrorHandler(error, requestOptions: RequestOptions | RequestOptions | ForkOptions) {
    // turn off the active components.
    this.turnOffExternalComponents(requestOptions);
    // create alert for the client.
    this.alertCtrl.create({
      title: "Error!",
      subTitle: error.message
    }).present();
  }

  /**
   * terminates the view components which are related to the request,
   * @param {RequestOptions | ForkOptions} requestOptions
   */
  private turnOffExternalComponents(requestOptions: RequestOptions  | ForkOptions) {
    // set the refresher to complete
    if(requestOptions.refresher) {
      requestOptions.refresher.complete();
      // after refreshing, enable the infinit scroller.
      if (requestOptions.infinitScroller) {
        requestOptions.infinitScroller.enable(true);
      }
    }
    // set the infinit Scroller to complete.
    // and turn on the infinit scroller.
    if(requestOptions.infinitScroller) {
      requestOptions.infinitScroller.complete();
    }

    // check if there is loader, and turn it off.
    if(requestOptions.loader) {
      requestOptions.loader.dismissAll();
    }
  }
}

this service will be used by other services in the application to provide http communication with server side.
for now, i don't now if this is a good practice or not. any guidance or help will be appreciated :) thanks.

3 Answers 3

2

You could create a component with the infinitscroller in it to wrap your content. Finally your service should expose an API to subscribe to results of http service. Within this component you can subscribe to thoise events and control the scrollerwith data binding. That's the angular pattern.

Something like that:

public errors = new ReplaySubject<any>(1);
public void doRequest(): Observable<any> {
    this._http.get('').catch((err, source) => {
        this.errors.next(err);
        return source;
    });
}

in Component:

public ngOnInit() {
    this._httpService.errors.subscribe(error => {
        this.scrollerEnabled = false;
    })
}
Sign up to request clarification or add additional context in comments.

2 Comments

thanks man, i guess i have to cover RxJs first and then start building these services.
For sure. Imagin rxjs as the central component in angular web development. reactive programming is the key for successful consummation of async services. To dive in, this is a good entry point: rxmarbles.com
1

hard to say without knowing the project architecture. But to me, it seems not to be the right way.

Controls and UI are controlled from within a communication service.

Further, there is much logic not really doing anything. This class will be extended to infinite while implementing all conditions.

Take a look at the RequestOptions interface. From a pure modelling view, an infinite scroller should not be part of this interface.

Work with subscriptions, async pipes and data binding.

3 Comments

you know that this service is singleton, and will be used by the whole project, instead of repeated this logic in each component used infinite scroller and other options, i did centralize this logic in one service can u explain more about "Work with subscriptions, async pipes and data binding" and how to use them in this case?
thanks alot for this great explanation. can u provide me with a link or "title" for this field, i mean the field of creating the right flow and design patterns.
Sorry. Text too long for comment on comment. See below.
0

Dynamic service for http request

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class HttpclientService {
  constructor(private _http: HttpClient) {}

  getRequest(url: string):Observable<any> {
    return this._http.get(url)
    .pipe(
      catchError(this.handleError)
    );
  }

  postRequest(url: string, data:any,options?:any):Observable<any> {
    return this._http.post(url,data,options)
    .pipe(
      catchError(this.handleError)
    );
  }

  updateRequest(url: string, data:any,options?:any):Observable<any> {
    return this._http.put(url,data,options)
    .pipe(
      catchError(this.handleError)
    );
  }

  deleteRequest(url: string) {
    return this._http.delete(url)
    .pipe(
      catchError(this.handleError)
    );
  }


 handleError(error: HttpErrorResponse) {
    if (error.status === 0) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong.
      console.error(
        `Backend returned code ${error.status}, body was: `, error.error);
    }
    // Return an observable with a user-facing error message.
    return throwError(() => new Error('Something bad happened; please try again later.'));
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.