26

I want to know when internet connection is lost and regained, so that I can toggle between an alert saying "whoops, no internet" and a Google map or a grid containing data derived from my server.

This related question and this other related question think that they have the answer, but they do not.

Their solution works with Chrome Version 34.0.1847.137 m, MS IE v11.0.x.x, but NOT with FireFox v29.0.1, so I am seeking a solution which works with all of those three browsers.


[Update] AS @Quad points out there are different ways of defining what it means to be online. I am definign it as "can I fetch the data which I need to show to my user or not?".


I have several services, which are responsible for fetching data from several servers (what's best? A single, parameterized, service? One service per server? Or one service per type of data per server? I am thinking the latter, as each service can then map to a controller which maps to a view. But I am new to Angular, so may well be wrong).

Additionally, I had coded a service which is responsible for attempting to reconnect when connection is lost.

Anyone who tries an $http.get and gets 404 can invoke the service which would
1) broadcast that there was no internet (so that no one else would try to connect)
2) regularly attempt to connect to my server and
3) when successful, stop the connection attempts and broadcast that the app is now online again.

However, that seemed very klunky and the solution offered in the two related questions seemed elegant - except for FF :-(

I cannot be reinventing the wheel here. How do others do it? In fact, I am surprised that there is not already an "official" Angular solution

Community
  • 1
  • 1
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
  • 1
    I believe I'm missing a lot of context here. I would recommend explaining your problem without referring to other questions. – JeffryHouser May 25 '14 at 03:05
  • 1
    There is no official angular solution because what one considers "online" may change from project to project. If you truly want to check user's actual internet connection you have to stick to the html5 apis which(as you have already found out) may or may not work on all browser. But if you want to check connection between your server and the client you can write an interceptor and if your requests time-out or can't connect to your server, display an error and retry request in certain intervals. – Quad May 27 '14 at 12:57
  • +1 good points. Let me rephrase it as "can I fetch the data which I need to show to my user, or not?". – Mawg says reinstate Monica May 28 '14 at 03:50
  • Also remember that the failure to connect to one server, does not mean that the internet is out. It can mean many things, like that one server is out, not all servers. Or maybe that it had a bad request that locked you out (some of our servers do that) or that request got dropped by the server, or .... you get the point. – Jdahern Jun 02 '14 at 18:21

3 Answers3

26

The best way that I would know would be to intercept the HTTP handler, if its a 401 / 501/ etc then to handle it according

ex:

angular.module('myApp', ['myApp.services'], 
    function ($httpProvider) {

    var interceptor = ['$rootScope', '$q', function ($rootScope, $q) {

        function success(response) {
            return response;
        }

        function error(response) {
            var status = response.status; // error code

            if ((status >= 400) && (status < 500)) {
                $rootScope.broadcast("AuthError", status);
                return;
            }

            if ( (status >= 500) && (status < 600) ) {
                $rootScope.broadcast("ServerError", status);
                return;
            }

            // otherwise
            return $q.reject(response);

        }

        return function (promise) {
            return promise.then(success, error);
        }

    }];
    $httpProvider.responseInterceptors.push(interceptor);

then in your code that listens for the on of, just add in

$rootScope.$on("ServerError", someServerErrorFunction);

You could also add an internal flag and broadcast only when that flag changed.

But if you are looking for a solution where the user is not communicating with the server too frequently, you could add a section that pings the server every minute or so, but that may not be responsive as you like.

Pardeep Jain
  • 84,110
  • 37
  • 165
  • 215
Jdahern
  • 1,196
  • 9
  • 14
  • 1
    Well done, that new guy! I have implemented it exactly as you described. I am still surprised that their is no "official" Angular way to do this. Maybe you post will become the "official" way? – Mawg says reinstate Monica Jun 03 '14 at 04:38
  • 2
    Thanks, I have gotten so much help from SO, Its nice to be able to start helping back – Jdahern Jun 03 '14 at 17:48
  • Thanks benek for the typo fix. – Jdahern Oct 21 '14 at 22:52
  • 2
    So if you get an HTTP status code of >= 500 and < 600 you know the user is offline? (I was directed here from my [similar question](http://stackoverflow.com/questions/27451186/http-error-handling-distinguishing-user-offline-from-other-errors)) – KnewB Dec 22 '14 at 15:45
  • @KnewB no, a 5XX is a server error, it could be anything from the user is offline to the server your trying to connect to does not like you. If you'r looking to see if the user can connect to the internet in general, then this would only be a starting point. On 5xx errors, you would then check 2 or 3 other servers. Even then, that may not tell the truth. But, if your trying to see if the user can connect to your server (usually more important), then this what you want. – Jdahern Dec 22 '14 at 21:49
  • 1
    What should I change in this code if I'm using Cordova that have a `navigator.connection.type` var which is permanently updated ('none', 'wifi', etc.) ? – David Dahan Feb 10 '15 at 17:37
  • @DavidW. Im not 100% positive since I don't use Cordova, but I would think the best approach would be to make something that watches the navigator.connection.type and then sends a broadcast $rootScope.broadcast("connectionType", navigator.connection.type); – Jdahern Feb 10 '15 at 22:02
  • 2
    Nice answer! +1. I think that you should update it though, since `responseInteceptors` are deprecated in the current angular 1.x version. From my tests, if an angular http request fails because of no internet connection, the status returned is `-1`. It may not be a standard response, but this was my approach on detecting no connectivity in cordova-angular apps (using angular 1.4.8) – gion_13 Jan 14 '16 at 11:38
  • An alternative to this `responseInteceptors` solution is found e.g. in an answer for [this question](http://stackoverflow.com/questions/23804981/alternative-of-httpprovider-responseinterceptors). – jsruok Feb 17 '16 at 10:03
7

In my applications, I have a /ping endpoint that I poll every X seconds. This of course may not be suitable for large-scale apps.

angular.module("pingMod", [])
    .run(function ($http, $interval) {
        var TIME = 60000;
        function ping() {
            $http.get("/ping");
        }
        $interval(ping, TIME);
    });

I use this in combination with what @Gil suggested, an HTTP interceptor. When we get a 0 status, we are offline. This is triggered for any AJAX call that uses $http.

Here is code for the interceptor using $http. You may of course decide you want to use Restangular instead, but it depends on your app needs.

angular.module("myModule", [])
    .config(function ($httpProvider) {
    var interceptor = [
        '$q',
    function ($q) {
        return function (promise) {
            return promise.then(function (response) {
                return response;
            }, function (response) {
                if (response.status === 0) {
                    // We're offline!
                }
                return $q.reject(response);
            });
        };
    }];
    $httpProvider.responseInterceptors.push(interceptor);
})
Sam P
  • 1,821
  • 13
  • 26
5

You can detect disconnect either by polling your server, or just waiting for a failed response. The latter approach is preferable unless you have special requirements. Once a disconnect is detected, you will need to use polling to detect re-connection.

An elegant way to implement this in angular is to monitor all network activity with $http, the injectable service through which all XHR activity flows. Restangular abstracts this out for you with:

  • RestangularProvider.addFullRequestInterceptor: Gives you full access to the request before sending any data to the server.

  • RestangularProvider.setErrorInterceptor: Called after each error response from the server.

Here is the pseudo-code to implement a status watcher using Restangular:

var last_request

RestangularProvider.addFullRequestInterceptor:
  last_request = Save last request

RestangularProvider.setErrorInterceptor:
    - Display the 'offline' status message to user
    - Use $interval to periodically re-submit last_request until successful
    - When periodic re-submission succeeds, hide 'offline' status message

Even though you didn't specifically ask for an answer utilizing Restangular, you did say you were looking for an established pattern and Restangular has some very easy-to-use yet powerful patterns built-in. Of course the same idea presented here could instead be implemented with just $http.

Gil Birman
  • 35,242
  • 14
  • 75
  • 119