I am working on a case where during a network connection we sometimes might have a limited internet connectivity where we unable to get response from the server or failed response as HttpError.
I hereby trying to ping the URL every second to check whether we are getting response or not, for this
I am trying this code, this is working fine in online method but when i am turning my internet of is doesn't return me false value.
fetch-data.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpResponse, HttpErrorResponse } from '#angular/common/http';
import { Posts } from './posts';
import { Observable, interval, throwError, of } from 'rxjs';
import { take, exhaustMap, map, retryWhen, retry, catchError, tap, mapTo, } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class FetchDataService {
public url = 'https://jsonplaceholder.typicode.com/posts';
constructor(private _httpClient: HttpClient) { }
getData() {
const ob = interval(1000);
return ob.pipe(
exhaustMap(_ => {
return this._httpClient.get<Posts[]>(this.url, { observe: 'response' });
}),
map(val => {
if (val.status === 200)
return true;
throw val;
}),
retryWhen(errors => {
return errors.pipe(map(val => {
if (val.status === 0)
return false;
}))
})
);
}
// private handleError(error: HttpErrorResponse) {
// if (error.error instanceof ErrorEvent) {
// // A client-side or network error occurred. Handle it accordingly.
// console.error('An error occurred:', error.error.message);
// } 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}`);
// if (error.status !== 200)
// return of(false);
// }
// // return an observable with a user-facing error message
// return throwError(
// 'Something bad happened; please try again later.');
// };
}
pulldata.component.html
import { Component, OnInit } from '#angular/core';
import { FetchDataService } from '../fetch-data.service';
import { Observable } from 'rxjs';
import { Posts } from '../posts';
#Component({
selector: 'app-pulldata',
templateUrl: './pulldata.component.html',
styleUrls: ['./pulldata.component.css']
})
export class PulldataComponent implements OnInit {
public data;
public error = '';
constructor(private _fecthDataServe: FetchDataService) { }
ngOnInit() {
this._fecthDataServe.getData().subscribe(val => {
this.data = val;
console.log(this.data);
});
}
}
what would be the best solution to check the internet connectivity in this manner?
My personal preference would be to not do this with HTTP because of data overhead. Every HTTP request will contain cookie data and other headers that are often useless in these kinds of scenarios.
Is it possible for you to use Web Sockets? With these, you can open up a connection to the server that, unlike HTTP, doesn't have to close. It can remain open forever. And you can subscribe to events to get notified about connection losses. Web Sockets also have the added benefit that it's a new protocol based on TCP, it's not HTTP, resulting in a lot less network data will have to be send.
let socket = new WebSocket('wss://yourserver/socket...');
socket.addEventListener('open', () => console.log('connection has been opened'));
socket.addEventListener('close', () => console.log('connection has been closed'));
In your situation, you might also want to check out the Reconnecting WebSocket, which reconnects when the connection drops. You could also write this small wrapper yourself, of course.
Also, what might even be a simpler solution. You can subscribe to online/offline events on the window object: read more on MDN
function updateOnlineStatus(event) {
var condition = navigator.onLine ? "online" : "offline";
// ...do something with the new status
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
Both of these solutions should be easily wrappable in an Angular service, but let me know if that works out and/or if these solutions are an option for you.
Related
I am trying to make an interceptor to refresh the token, but it throws me this error and I don't know why
ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
token-interceptor.service.ts
import { Injectable } from '#angular/core';
import { AuthService } from './auth.service';
import { HttpClient, HttpErrorResponse, HttpHandler, HttpInterceptor, HttpRequest } from '#angular/common/http';
import { environment } from 'src/environments/environment';
import { catchError, map} from 'rxjs/operators';
import { throwError } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class TokenInterceptorService implements HttpInterceptor {
constructor(
private auth: AuthService,
private http: HttpClient
) { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(
catchError((err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.url.includes('signin') || err.url.includes('refreshToken')) {
return next.handle(req)
}
//if error is not about authorization
if (err.status !== 401) {
return next.handle(req)
}
this.renewToken(req).subscribe(request => {
return next.handle(request)
})
} else {
return throwError(err)
}
})
)
}
renewToken(req: HttpRequest<any>) {
return this.http.get(`${environment.API_URL}/refreshToken`, { withCredentials: true }).pipe(
map((res: any) => {
//update access token
this.auth.setToken(res.token)
return req.clone({
setHeaders: {
authorization: `Bearer ${res.token}`
}
})
})
)
}
}
Ignore this: It looks like your post is mostly code; please add some more details. It looks like your post is mostly code; please add some more details.
this piece of code is wrong:
this.renewToken(req).subscribe(request => {
return next.handle(request)
})
istead it should be:
return this.renewToken(req).pipe(switchMap(request => next.handle(request)));
you are just returning nothing in your variant, that is why it doesn't work.
also the whole logic of token interpceptor seems weird to me. I believe you should rethink about how you want it to work. for now as I see you sending request without token and in almost all cases you are sending it again unmodified, and the one that I fixed above will send it again with token. Wouldn't it be right to add token every time, and only send it 2nd time if token is outdated?
I am making a simple delete request from my angular app but nothing is happening and no error is appearing. My service code is as follows :
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class TodoService {
todoUrl = 'https://example.herokuapp.com/api/todoDB/';
constructor(private http: HttpClient) { }
getTodo() {
return this.http.get(this.todoUrl);
}
postTodo(todoObject: any) {
return this.http.post(this.todoUrl , todoObject);
}
deleteTodo(id: any) {
const url = `${this.todoUrl}${id}`;
console.log(url); // *** This is printing correct URL
return this.http.delete(url);
}
}
My getTodo() and postTodo() are working completely fine but the deleteTodo() method is not working and also it does not show any error either. When I put the URL from the console.log(url) in postman, it works but it is not working from my app.I am using the following code in my component to access the deleteTodo() method of my service :
removeTodo(i: any) {
this.todoService.deleteTodo(this.todoArray[i]._id);
}
My delete route of server :
// Delete Todo
router.delete('/:id' , (req , res) => {
Todo.findById(req.params.id)
.then((todo) => todo.remove().then(() => res.json({success : true})))
.catch(err => res.json({success : false}).status(404))
});
You need to subscribe to the Observable
Code Snippet for your problem:
removeTodo(i: any) {
this.todoService.deleteTodo(this.todoArray[i]._id).subscribe(e=>{
// Callback
// Perform Actions which are required after deleting the id from the TODO
});
}
Additional Reference:
https://www.pluralsight.com/guides/posting-deleting-putting-data-angular
https://angular.io/guide/http#making-a-delete-request
Modify your code to support catchError and throwError using pipe for debugging.
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
deleteTodo(id: any) {
const url = `${this.todoUrl}${id}`;
return this.http.delete(url).pipe(
catchError((err) => {
console.log('error caught in service')
console.error(err);
return throwError(err); //Rethrow it back to component
})
);
}
I'm confused about general good practice when it comes to error handling. For example, if I'm already catching the error in my service, do I still need to include the error handler in my subscription?
Here's my http method in my service. As you can see it calls catchError:
deleteTask(id: number): Observable<any>{
return this.http.delete(this.tasksUrl+'/'+`${id}`)
.pipe(
catchError(this.handleError)
);
}
private handleError(res: HttpErrorResponse | any) {
console.error(res.error || res.body.error);
return observableThrowError(res.error || 'Server error');
}
And in my component:
delete(id: number){
this.deleteService.deleteTask(id).subscribe(
(val) => {
/*post processing functionality not relevant to this question
*/
}
);
}
In the angular documentation https://angular.io/guide/observables the error handler is described as optional:
myObservable.subscribe(
x => console.log('Observer got a next value: ' + x),
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification')
);
So in my example, would including the error handler on my subscription add anything to my code? Like if I did:
delete(id: number){
this.deleteService.deleteTask(id).subscribe(
(val) => {
/*post processing functionality not relevant to this question
*/
},
err => console.error('Observer got an error: ' + err)
);
Would it catch anything my catchError didn't catch? It almost feels like it would be good practice to always include the error handler, so I don't know why it's marked as optional? When should one use the subscription error handler vs other forms of error handling?
It's all about how you want to handle error in your application,
if you want to throw a fancy error instead of the actual error comming back from server, you can have a error handler in service end and who ever consume your service will get the fancy error instead of actual error.
// copied from your question
deleteTask(id: number): Observable<any>{
return this.http.delete(this.tasksUrl+'/'+`${id}`)
.pipe(
catchError(this.handleError)
);
}
private handleError(res: HttpErrorResponse | any) {
console.error(res.error || res.body.error);
return observableThrowError(res.error || 'Server error');
}
if not, you dont need to handle error in your service code, let the consumer(service/component) handle it.
deleteTask(id: number): Observable<any>{
return this.http.delete(this.tasksUrl+'/'+`${id}`);
}
// component
...
this.service.deleteTask(id).subscribe(success,(err) => {
// example
alert(err.message);
});
...
To handle common http errors (500, 401, 403, 404) you can write a HttpInterceptor, so that you dont need to write the error handling logic everywhere.
import { Injectable } from '#angular/core';
import {
HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse
} from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
#Injectable()
export class MyAppHttpInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// redirect to login page
} else {
return throwError(error);
}
})
);
}
}
if you want to log the errors to server or if you want to show a custom console or error notification over the screen on development mode or for debugging, you can create a global error handler service extending the existing ErrorHandler servcie in angular.
import { ErrorHandler } from '#angular/core';
#Injectable()
export class GlobalErrorHandler implements ErrorHandler {
handleError(error) {
// your custom error handling logic
}
}
Would it catch anything my catchError didn't catch?
No, you are just passing the same error through. But if your subscription doesn't have an error handler you will get an exception if you don't handle it. So you should either have an error handler on your subscription or pass an observable with no data.
const { throwError, of } = rxjs;
const { catchError } = rxjs.operators;
throwError('error').pipe(catchError(error => {
console.log('Caught error - ', error);
return of(null);
})).subscribe(val => { console.log('No error handler needed'); });
throwError('error').pipe(catchError(e => {
console.log('Caught error - ', e);
return throwError(e);
})).subscribe(val => {}, error => { console.log('Subscription handled error - ', error); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>
I am using the code below to catch errors in my small Angular 6 app... I am catching the errors, I am showing info in the console the problem is that the toasts do not show unless I click somewhere in the application, they don't show up all by themselves. I am using the ToastrService in other parts of the application and whenever I call it like I do it here it shows toats without having to click on anything.
Any idea what might be causing this behaviour?
import { Injectable, ErrorHandler, Injector } from '#angular/core';
import { Router } from '#angular/router';
import { HttpErrorResponse } from '#angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { AppSettings } from '../../app.settings';
#Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) {}
public handleError(error: any) {
const router = this.injector.get(Router);
const settings = this.injector.get(AppSettings)
const toastr = this.injector.get(ToastrService);
console.log(`Request URL: ${router.url}`);
console.log(error);
if (error instanceof HttpErrorResponse) {
console.log("it is");
if (error.status === 401) {
router.navigate(['/login']);
settings.settings.setLoadingSpinner(false);
toastr.error('Logged in user no longer authenticated on server.', 'Unable to connect to server');
} else if (error.status === 404) {
console.log("it is 404");
toastr.error('Unable to connect to server. Missing or wrong URL, please try again', 'Unable to connect to server');
settings.settings.setLoadingSpinner(false);
} else if (error.status === 0) {
toastr.error('Server appears to be temporary unavailable', 'Unable to connect to server');
settings.settings.setLoadingSpinner(false);
} else if (error.status === 500) {
toastr.error('Server appears to be temporary unavailable', 'Unable to connect to server');
settings.settings.setLoadingSpinner(false);
}
} else {
console.error(error);
toastr.error('An error has occured', 'Sorry');
}
}
}
I have added the provider in the module also to use this class as error handler.
please include the toaster service in constructor and run. As per my understanding, while service get instantiated, toaster should have to get initiated.
I believe you can import and include NgZone in your constructor to have this work as intended:
constructor(private injector: Injector, private zone: NgZone) {}
Then wrap your toastr calls:
this.zone.run(() => toastr.error('message', 'title'));
TL;DR;
Why subscribing to an Observable in an http interceptor produces duplicate http requests to server?
Sample code:
doGetWithInterceptor() {
console.log("Http get with interceptor -> 2 http calls ?? Why?");
this.http_interceptor_get("http://ip.jsontest.com/").subscribe(data => {
console.log("But only one block of data received:", data);
this.result= data.ip;
});
}
http_interceptor_get(url : string) {
let req= this.http.get(url).map(res => res.json());
req.subscribe((data) => {
console.log("[HttpInterceptor]");
});
return req;
}
Full details:
I use an http interceptor service in my Ionic 2 project to globally detect errors, authentication, and more...
But doing so, I am seeing duplicate http requests to the server.
I have an small test App starting from a blank Ionic 2 template:
Which clearly shows the problem in Firebug:
First request (it's ok, single) if using the GET button.
Second request (which duplicates) is using the "Get with interceptor" button.
Meanwhile, the code in the subscription part is executed only once, as it should.
The home.ts code is as follows:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
result : string = "???";
constructor(public navCtrl: NavController, public http: Http) {
}
http_get(url : string) {
return this.http.get(url).map(res => res.json());
}
http_interceptor_get(url : string) {
let req= this.http.get(url).map(res => res.json());
req.subscribe((data) => {
console.log("[HttpInterceptor]");
});
return req;
}
doGet() {
console.log("Normal http get -> 1 http call");
this.http_get("http://ip.jsontest.com/").subscribe(data => {
console.log("One block of data received:", data);
this.result= data.ip;
});
}
doGetWithInterceptor() {
console.log("Http get with interceptor -> 2 http calls ?? Why?");
this.http_interceptor_get("http://ip.jsontest.com/").subscribe(data => {
console.log("But only one block of data received:", data);
this.result= data.ip;
});
}
doClearResult() {
this.result= "???";
}
}
Its because you are not really intercepting. You are simply subscirbing to the request twice.
http_interceptor_get(url : string) {
let req= this.http.get(url).map(res => res.json());
req.subscribe((data) => { //1st subscription - 1st call
console.log("[HttpInterceptor]");
});
return req; //return original request
}
Then you are subscribing again in doGetWithInterceptor() to your http req.
If you want to log details of call, you can use do().
http_interceptor_get(url : string) {
//return http call
return this.http.get(url).map(res => res.json())
.do(data=>{
//do checks.
return data; //be sure to return data so it is passed on to subscription.
});
}
Then call in your doGetWithInterceptor()