How to show spinner only if data are fetched from Http service? - javascript

I have to show a spinner only during http service call, and dismiss it when my component receives data.
I wrote a little cache service in order to fetch data from http service only the first time, and load that data from the cache during every other call, avoiding to call another time the http service.
The service is working as expected,but what if I'd like to show the spinner only during the http call and not when data are fetched from cache?
This is my component's code, it works when getReviewsCategory(this.id) method of my service calls http service, but when it fetches from cache the spinner is never dismissed.
Data are loaded in correct way in the background, but the spinner keeps going.
presentLoading() method is in ngOnInit so it's called everytime, what if I want to call it only when data are fetched from cache? How my component could know it?
ngOnInit() {
this.presentLoading();
this.CategoryCtrl();
}
CategoryCtrl() {
this.serverService.getReviewsCategory(this.id)
.subscribe((data) => {
this.category_sources = data['value'];
this.stopLoading();
});
}
async presentLoading() {
const loadingController = this.loadingController;
const loadingElement = await loadingController.create({
spinner: 'crescent',
});
return await loadingElement.present()
}
async stopLoading() {
return await this.loadingController.dismiss();
}
}
EDIT1: this is the CacheService:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class CachingService {
constructor() { }
private _cache = {};
isCashed(url: string) {
return this._cache[url];
}
getData(url: string) {
return this._cache[url];
}
setData(url) {
return (data) => {
if (data && (data instanceof Error) === false) {
this._cache[url] = data;
};
}
}
reset() {
this._cache = {};
}
}
And this is the server service's method:
getReviewsCategory(cat_id) : Observable<any> {
if (this._c.isCashed(url)) {
return of(this._c.getData(url));
}else{
var modeapp = window.sessionStorage.modeapp;
var typemodeapp = typeof(window.sessionStorage.modeapp);
if (modeapp === "online") {
let promise = new Promise ((resolve, reject) => {
this.httpNative.get(url, {}, {}).
then((data) => {
let mydata = JSON.parse(data.data);
console.log("Data from HTTP: ");
console.log(mydata);
resolve(mydata);
}, (error) => {
console.log("error in HTTP");
reject(error.error);
}
);
});
var observable = from(promise);
}
}
return observable
.pipe(
tap(this._c.setData(url))
);

I can see you're returning an observable from the service, you can try the following to see if this helps.
CategoryCtrl() {
this.serverService.getReviewsCategory(this.id)
.subscribe((data) => {
this.category_sources = data['value'];
this.stopLoading();
},
(error) => console.log(error),
() => this.stopLoading(); // This always execute
);}
Docs: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-subscribe
However, I believe the problem may come from the object you're calling .dismiss()
from. You should be calling dismiss on the instance of the element and not the object itself.
let loadingElement: Loading = null;
async presentLoading() {
const loadingController = this.loadingController;
this.loadingElement = await loadingController.create({
spinner: 'crescent',
});
return await loadingElement.present()
}
async stopLoading() {
return await this.loadingElement.dismiss();
}

You can use an HttpInterceptor class to intercept all http calls, and in the intercept method, you can stop and start a spinner.
Broadly speaking, the structure is:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Start the spinner.
return next.handle(req).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// Stop the spinner
}
return event;
})
);

Related

automatically perform action on Promise method resolve

I have a MobX data store, called BaseStore that handles the status of an API request, telling the view to render when request is in progress, succeeded, or failed. My BaseStore is defined to be:
class BaseStore {
/**
* The base store for rendering the status of an API request, as well as any errors that occur in the process
*/
constructor() {
this._requestStatus = RequestStatuses.NOT_STARTED
this._apiError = new ErrorWrapper()
}
// computed values
get requestStatus() {
// if there is error message we have failed request
if (this.apiError.Error) {
return RequestStatuses.FAILED
}
// otherwise, it depends on what _requestStatus is
return this._requestStatus
}
set requestStatus(status) {
this._requestStatus = status
// if the request status is NOT a failed request, error should be blank
if (this._requestStatus !== RequestStatuses.FAILED) {
this._apiError.Error = ''
}
}
get apiError() {
// if the request status is FAILED, return the error
if (this._requestStatus === RequestStatuses.FAILED) {
return this._apiError
}
// otherwise, there is no error
return new ErrorWrapper()
}
set apiError(errorWrapper) {
// if errorWrapper has an actual Error, we have a failed request
if (errorWrapper.Error) {
this._requestStatus = RequestStatuses.FAILED
}
// set the error
this._apiError = errorWrapper
}
// actions
start = () => {
this._requestStatus = RequestStatuses.IN_PROGRESS
}
succeed = () => {
this._requestStatus = RequestStatuses.SUCCEEDED
}
failWithMessage = (error) => {
this.apiError.Error = error
}
failWithErrorWrapper = (errorWrapper) => {
this.apiError = errorWrapper
}
reset = () => {
this.requestStatus = RequestStatuses.NOT_STARTED
}
}
decorate(BaseStore, {
_requestStatus: observable,
requestStatus: computed,
_apiError: observable,
apiError: computed,
})
That store is to be extended by all stores that consume API layer objects in which all methods return promises. It would look something like this:
class AppStore extends BaseStore {
/**
* #param {APIObject} api
**/
constructor(api) {
super()
this.api = api
// setup some observable variables here
this.listOfData = []
this.data = null
// hit some initial methods of that APIObject, including the ones to get lists of data
api.loadInitialData
.then((data) => {
// request succeeded
// set the list of data
this.listOfData = data
}, (error) => {
// error happened
})
// TODO: write autorun/reaction/spy to react to promise.then callbacks being hit
}
save = () => {
// clean up the data right before we save it
this.api.save(this.data)
.then(() => {
// successful request
// change the state of the page, write this.data to this.listOfData somehow
}, (error) => {
// some error happened
})
}
decorate(AppStore, {
listOfData : observable,
})
Right now, as it stands, I'd end up having to this.succeed() manually on every Promise resolve callback, and this.failWithMessage(error.responseText) manually on every Promise reject callback, used in the store. That would quickly become a nightmare, especially for non-trivial use cases, and especially now that we have the request status concerns tightly coupled with the data-fetching itself.
Is there a way to have those actions automatically happen on the resolve/reject callbacks?
Make an abstract method that should be overridden by the subclass, and call that from the parent class. Let the method return a promise, and just hook onto that. Don't start the request in the constructor, that only leads to problems.
class BaseStore {
constructor() {
this.reset()
}
reset() {
this.requestStatus = RequestStatuses.NOT_STARTED
this.apiError = new ErrorWrapper()
}
load() {
this.requestStatus = RequestStatuses.IN_PROGRESS
this._load().then(() => {
this._requestStatus = RequestStatuses.SUCCEEDED
this._apiError.error = ''
}, error => {
this._requestStatus = RequestStatuses.FAILED
this._apiError.error = error
})
}
_load() {
throw new ReferenceError("_load() must be overwritten")
}
}
class AppStore extends BaseStore {
constructor(api) {
super()
this.api = api
this.listOfData = []
}
_load() {
return this.api.loadInitialData().then(data => {
this.listOfData = data
})
}
}
const store = new AppStore(…);
store.load();
MobX can update data that is asynchronously resolved. One of the options is to use runInAction function
example code:
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// after await, modifying state again, needs an actions:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
You can read more in official documentation: Writing asynchronous actions

Inconsitant mergeMap behaviour

I am currently working on a file uploading method which requires me to limit the number of concurrent requests coming through.
I've begun by writing a prototype to how it should be handled
const items = Array.from({ length: 50 }).map((_, n) => n);
from(items)
.pipe(
mergeMap(n => {
return of(n).pipe(delay(2000));
}, 5)
)
.subscribe(n => {
console.log(n);
});
And it did work, however as soon as I swapped out the of with the actual call. It only processes one chunk, so let's say 5 out of 20 files
from(files)
.pipe(mergeMap(handleFile, 5))
.subscribe(console.log);
The handleFile function returns a call to my custom ajax implementation
import { Observable, Subscriber } from 'rxjs';
import axios from 'axios';
const { CancelToken } = axios;
class AjaxSubscriber extends Subscriber {
constructor(destination, settings) {
super(destination);
this.send(settings);
}
send(settings) {
const cancelToken = new CancelToken(cancel => {
// An executor function receives a cancel function as a parameter
this.cancel = cancel;
});
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.next([null, resp.data]))
.catch(e => this.next([e, null]));
}
next(config) {
this.done = true;
const { destination } = this;
destination.next(config);
}
unsubscribe() {
if (this.cancel) {
this.cancel();
}
super.unsubscribe();
}
}
export class AjaxObservable extends Observable {
static create(settings) {
return new AjaxObservable(settings);
}
constructor(settings) {
super();
this.settings = settings;
}
_subscribe(subscriber) {
return new AjaxSubscriber(subscriber, this.settings);
}
}
So it looks something like this like
function handleFile() {
return AjaxObservable.create({
url: "https://jsonplaceholder.typicode.com/todos/1"
});
}
CodeSandbox
If I remove the concurrency parameter from the merge map function everything works fine, but it uploads all files all at once. Is there any way to fix this?
Turns out the problem was me not calling complete() method inside AjaxSubscriber, so I modified the code to:
pass(response) {
this.next(response);
this.complete();
}
And from axios call:
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.pass([null, resp.data]))
.catch(e => this.pass([e, null]));

Promise 'finally' callback equivalent in RxJS

I have a component that fetches some data from an API. The component has a loading member variable, which is used to determine whether a loading icon should be shown. When the data fetch completes, loading is set to false, in order to hide the loading icon. This should happen regardless of whether an error occurred or not.
Currently, I have implemented this as follows:
export class MyComponent implements OnInit {
loading = true;
data;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.getData().subscribe(data => {
this.data = data;
this.loading = false;
},
() => {
this.loading = false;
});
}
}
I'm wondering if there is a way to eliminate the duplication of this.loading = false. If I were using Promises, this is what I'd use the Promise.finally() callback for -- but I'm not. Is there an equivalent in RxJS, or a better way to implement this?
As of RXJS 5.5, use the "finalize" operator:
ngOnInit() {
this.dataService.getData()
.pipe(
finalize(() => this.loading = false)
)
.subscribe(data => {
this.data = data;
});
}

How do I return the value from a callback in this promise?

In my angularJS 4 app, I'm using a cordova accelerometer plugin called device motion for something. The api has a function call getAcceleration(successCallback,errorCallback). So i created a service that looks like this
#Injectable()
export class AccelerometerService {
private acc : any;
constructor(private winRef:WindowRef) {
this.acc = this.winRef.nativeWindow.plugins.navigator.accelerometer;
this.onSuccess = this.onSuccess.bind(this);
this.onError = this.onError.bind(this);
}
getAcceleration(){
return new Promise ((resolve, reject) =>{
resolve(this.acc.getCurrentAcceleration(this.onSuccess,this.onError));
});
}
onSuccess(acceleration){
return acceleration; // value that I want returned to my component
}
onError(){
return 'error';
}
}
In my component, I do this to try to get the return value from onSuccess callback function, however the response is undefined
this.accelerationService.getAcceleration().then((res) =>{
console.log(res); // res is undefined
})
How can I resolve this issue?
Instead of:
resolve(this.acc.getCurrentAcceleration(this.onSuccess,this.onError));
Do:
this.acc.getCurrentAcceleration(resolve, reject);
You don't need this.onSuccess.
Try like this :
getAcceleration(): Promise<any> {
return new Promise<any>((resolve, reject) => {
this.acc.getCurrentAcceleration()
.success(data => {
resolve(<any>data);
})
.error(() => {
reject('Failed to load profile');
});
})
}

Custom XHR Callbacks Handler

I'm working on a small typescript http library for my friend to simplify the http requests a little bit. I want my friend to be able to send Asynchronous POST requests using method post() from the Http object I've created.
I want to accomplish something similar to subscribe() method in Angular 2. What I mean is I want to create a function, which would be responsible for callbacks (3 types - success, error, complete) and I would use it on my Http's post() method. Here is what I have until now.
Basically here is the written idea:
Http:
import { IHeader } from 'interfaces';
import { SubscribeAble } from 'subscribeAble';
class Http {
http: XMLHttpRequest;
constructor() {
this.http = new XMLHttpRequest;
}
post(url: string, data: Object, headers?: Array<IHeader>) {
this.http.open('POST', url);
if(headers) {
for(let header of headers) {
this.http.setRequestHeader(header.name, header.value);
}
}
this.http.send(JSON.stringify(data));
return new SubscribeAble(this.http);
}
}
SubscribeAble:
export class Subscribe {
http: XMLHttpRequest;
constructor(http) {
this.http = http;
}
subscribe(success: (success) => void, error?: (error) => void, complete?: () => void) {
this.http.onload = success;
if(error) { this.http.onerror = error; }
if(complete) { this.http.onreadystatechange = complete; }
}
}
What I need now is the idea of how to inject the data to functions in subscribe() method... a bit more simple: I want 'success' variable to have this.http.response value in function (success) => {}. Thank you in advance.
I finally figured out how to repair the subscribe method. I used callbacks to achieve what I wanted to. Here is the code:
subscribe(success: (success) => void, error?: (error) => void, complete?: () => void) {
let callback = (cb: (res) => void) {
return callback(this.http.response);
}
this.http.onload = () => {
return callback(success);
}
if(error) {
this.http.onerror = () => {
return callback(error);
}
}
if(complete) { this.http.onloadend = complete; }
}
I think you can do something like this:
subscribe(success: (success) => void, error?: (error) => void, complete?: () => void) {
this.success = success;
this.error = error;
this.complete = complete;
this.http.onload = this.onload;
if(error) { this.http.onerror = this.onerror; }
if(complete) { this.http.onreadystatechange = this.oncomplete; }
}
onload() {
if (this.http.status === 200) {
this.success(this.response);
} else {
if (this.error)
this.error(this.http.statusText);
}
}
}
you set the functions the user send you in subscribe as class variables, and call them with the data you want to send as their parameters.
and you can create the onerror and oncomplete method for the other 2 functions

Categories