I'm using Ionic 2 which sits on top of Angular 2.
I am using a service in my app to handle all API requests. Some of those requests carry an Authorization header. My API can return a 401 if the auth fails, and I am currently handling that by showing an error to the user and navigating them to the login screen. That all works great and I publish an event for these errors which I subscribe to elsewhere to handle the logic (as I can't use navigation controllers in a service).
Code for api.js
service:
@Injectable()
export class Api {
base_url: string = 'https://***.com';
url: string = this.base_url + '/api/v1';
authurl: string = this.base_url + '/oauth/token';
grant_type: string = 'password';
client_id: string = '1';
client_secret: string = '***';
access_token: string;
constructor(
public http: Http,
private storage: Storage,
public events: Events) {
// Grab access token and store it
storage.get('access_token').then((val) => {
this.access_token = val;
});
}
// Performs a GET request with auth headers
get(endpoint: string, params?: any) {
if(!params) {
params = [];
}
params['grant_type'] = this.grant_type;
params['client_id'] = this.client_id;
params['client_secret'] = this.client_secret;
let headers: Headers = this.getHeaders();
return this.getApiToken().flatMap(data => {
headers.append('Authorization', 'Bearer ' + data);
let options = new RequestOptions({ headers: headers });
// Support easy query params for GET requests
if (params) {
let p = new URLSearchParams();
for (let k in params) {
p.set(k, params[k]);
}
// Set the search field if we have params and don't already have
// a search field set in options.
options.search = !options.search && p || options.search;
}
return this.http.get(this.url + '/' + endpoint, options)
.catch((error: any) => {
if (error.status === 500) {
this.events.publish('api:generalError', error);
return Observable.throw(new Error(error.status));
}
else if (error.status === 401) {
this.events.publish('api:unauthorized', error);
return Observable.throw(new Error(error.status));
}
});
}).share();
}
}
The problem is down to the 'Loading...' dialog that I show to the user while the get
request is taking place. Before the method is called I create a loading dialog and dismiss it on success or failure. The problem is that I don't have any scope of this inside api.js
in order to dismiss it when a 401 or 500 is caught.
Here's a sample of my logic around this:
let loader = this.loadingCtrl.create({
content: "Please wait..."
});
loader.present();
this.trainingProgramme.get_programmes()
.map(res => res.json())
.subscribe((res) => {
this.currentItems = res.training_programmes;
}, (err) => {
// Error
console.log(err);
}, () => {
loader.dismiss();
});
I don't think it's important, but I also have a service for each entity which in turn calls api.js
service. In the above example it's this.trainingProgramme
, which looks like this:
get_programmes() {
let seq = this.api.get('training-programmes');
seq
.subscribe();
return seq;
}
I thought the way I approached this was all correct, however I can't see a way I can handle the 'Loading' issue.
Is there a way I can have the get
method continue within my app even after an error has been caught, so that my loader.dismiss()
code is ran within the correct scope?
I really don't want to have to use the loader inside the service (not sure I'm even able to?) as this seems like bad design, and I don't want to always show a loader, so it belongs in the controller.
You can put a try catch inside the catch block, or you can simply throw the exception again. Its better to have finally block with your try catch so that even if an exception occurs in the catch block, finally block code gets executed.
To achieve detecting an error event and then providing logic to handle the error, we will configure a parallel action below our custom connector call. Within each stream of the parallel action we then specify our Configure run after settings.
Using Exception Filters Exception filters can be used to handle unhandled exceptions which are generated in Web API. The exception filter can be able to catch the unhandled exceptions in Web API. This filter is executed when an action method throws the unhandled exception.
The simplest way of handling errors in Express applications is by putting the error handling logic in the individual route handler functions. We can either check for specific error conditions or use a try-catch block for intercepting the error condition before invoking the logic for handling the error.
What you need is the .finally
operator, which will execute always even if there's an error on the Observable.
The complete
callback (the last from the .subscribe
call) will not trigger if an error
happens.
Remember to include it:
import 'rxjs/add/operator/finally';
...
this.trainingProgramme.get_programmes()
.finally(() => loader.dismiss())
.map(res => res.json())
.subscribe((res) => {
...
}
, (err) => {
// Error
console.log(err);
});
By the way, consider updating your code from @angular/http
to the new HttpClient
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With