ngResource $promise error handling? - angularjs

I have the following controllers which all the ngResource services to get data.
.controller('venueCtrl', function ($scope, $stateParams, VenueService) {
$scope.venue = VenueService.get({ id: $stateParams.id });
})
.controller('tomorrowCtrl', function ($scope, EventService) {
var evts = EventService.query({ period: "Tomorrow" });
evts.$promise.then(function (response) { $scope.events = response; });
}).....
Now I need to add error handling (for example, display an alert box) for the error situations, e.g., no network, web service failed, etc. How to add the code to handle the errors?

You can use Interceptors this way:
angular.module('ngRessourceErrorsHandler', [])
.config(function($resourceProvider) {
angular.forEach($resourceProvider.defaults.actions, function(action) {
action['interceptor'] = {
responseError: function(httpResponse) {
//Do whatever you want here !
}
};
})
});
Please try this and let me know if this works for you. Don't forget to add ngRessourceErrorsHandler dependency to your module or just use config directly.

Related

Error: Can't find variable: WindowsAzure

I am using Azure Mobile services with ionic and I'm trying to create my client in a factory as a singleton. However when trying to use the client like I did normally I keep getting this error Error: Can't find variable: WindowsAzure. Thank you in advance.
Factory
.factory('client', [function(){
var myjsonObj = new WindowsAzure.MobileServiceClient('https://xxx.azurewebsites.net');
return myjsonObj;
}])
Controller where I call it
.controller('loginCtrl', ['$scope', '$stateParams', 'md5', 'Userid', '$state','$ionicSideMenuDelegate','$ionicLoading','client',// The following is the constructor function for this page's controller. See https://docs.angularjs.org/guide/controller
// You can include any angular dependencies as parameters for this function
// TIP: Access Route Parameters for your page via $stateParams.parameterName
function ($scope, $stateParams,md5,Userid,$state,$ionicSideMenuDelegate,$ionicLoading,client) {
$scope.UserInfo = {};
$ionicSideMenuDelegate.canDragContent(false);
$scope.show = function() {
$ionicLoading.show({
template: '<p>Loading...</p><ion-spinner></ion-spinner>'
});
};
$scope.hide = function(){
$ionicLoading.hide();
};
$scope.login =function(){
$scope.show($ionicLoading);
if ($scope.UserInfo.password == null){
$scope.UserInfo.password = "fa";
}
var query = client.getTable('clubUser')
.where({ Email: $scope.UserInfo.email, Password: md5.createHash($scope.UserInfo.password) })
.read()
.done(function(results) {
if(results[0] == undefined)
{
$scope.errorText = "Error: Wrong Username/Password";
$scope.$apply();
$scope.hide($ionicLoading);
}
else
{
$scope.hide($ionicLoading);
Userid.setJson(results[0].id);
$state.go('tabsController.qRCode');
}
}, function(error) {
console.dir(error);
$scope.hide($ionicLoading);
});
$scope.UserInfo.password = "";
};
}])
So after looking into the error and not finding any results I decided to add the mobile services plugin manually through adding the script in my index and now it works great! Thank you for helping
You need to ensure the app is in the "ready" state (i.e. all JavaScript libraries are loaded). In Ionic, it's best to use a factory to ensure a singleton. That will also assure you that it is loading at the right time.

Recursively intercepting $http

In order to better consume a hateoas enabled rest api I got the idea to intercept http calls and add some methods to my resource objects before returning them.
The idea is that if the resource has an array of links I'll add methods to easy further http requests.
The below code is push to the interceptors array of a $httpProvider and does pretty much what I want.
define(['angular'], function (angular) {
var $http = angular.injector(['ng']).get('$http');
function flattenLinks(links) {
for (var key in links) {
var rel = links[key].rel;
var href = links[key].href;
links[rel] = href;
}
}
return {
'response': function responseHandler(response) {
var data = response.data ? response.data : response;
if(typeof data === 'string' || !data.hasOwnProperty('links')) {
return response;
}
if(data.links instanceof Array) {
flattenLinks(data.links);
}
if(data instanceof Array) {
for(var key in data) {
responseHandler(data[key]);
}
}
data.hasLink = function(link) {
return link in data.links;
};
data.get = function(rel) {
return $http.get(data.links[rel]);
};
data.delete = function(rel) {
return $http.delete(data.links[rel]);
};
data.post = function(rel) {
return $http.post(data.links[rel], data);
};
data.put = function(rel) {
return $http.put(data.links[rel], data);
};
return response;
}
};
});
The problem is that when I use, as seen below, my added methods to do requests the response isn't handled by my interceptor. The $http.get, $http.delete, etc. done from within my interceptor isn't intercepted (naturally!).
vm.someResourceObj.get('self').then(function(response) {
console.log(response);
});
So the question is. How do I get the internal calls to $http handled?
First of all - this is not an answer to you question =)
But it's a kind of advice: man, you are really try to develop a bicycle.
According to developers guide you should use $resource instead of $http to interact with REST api. Especially with RESTful.
$resource service will provide you functionality to make exactly what you want - to make requests like User.get() and after this User.save(), User.update(), etc.
My suggest is to create a factory for each entity(like "user", "account", etc), or for each workflow(like "sign up").
'use strict';
angular.module('app.factories.user', ['ngResource'])
.factory('UserFactory', function ($resource) {
var _userApi = {
user: $resource('/user', {}, {})
};
return {
getCurrentUser: function () {
return _userApi.user.get(function (data) {
// here we got data.user;
}, function (response) {
console.error(response.status);
}).$promise;
}
};
})
;
And use it in a controller:
.controller('UserPageCtrl', function ($scope, UserFactory) {
var currentUser;
$scope.getCurrentUser = function () {
return UserFactory.getCurrentUser().success(function (user) {
//here we got user
currentUser = user;
//now we able do stuf like
//currentUser.save()
//etc.
}).error(function (err) {
console.error(err);
});
};
});
So, don't mind if examples above little bit... you know... let's say if you don't like it ))
Just take a look at $resource documentation (and this)
If you don't like resource, you can use third-party restanguar (or perhaps something else). But restangular is much more strict, so choose it only if you know for sure that your backend developers aren't idiots.
UPD1: About interceptors (I'll put here my working code for error intercepting, the idea of this code is to logout user when any request return 403 error status):
You are able to add all facctory to global $httpProvider(at module config section).
Like this:
app.config(function ($urlRouterProvider, $httpProvider) {
//Process backend errors
$httpProvider.interceptors.push('errorsFactory');
})
And here is errorsFactory:
'use strict';
angular.module('app.factories.errors', [])
.factory('errorsFactory', function ($q) {
var HTTP_STATUS = {
FORBIDDEN: 403
};
return {
response: function (response) {
if (response.status === HTTP_STATUS.FORBIDDEN) {
//logout here
}
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === HTTP_STATUS.FORBIDDEN) {
//logout here
}
return $q.reject(rejection);
}
};
})
;
Use $injector to get $http instead of accessing it via angular..
Detailed Answer:
The problem is how you inject $http. It seems that you get some $http that is outside of your app (I can't exactly say why, though). var $http = angular.injector(['ng']).get('$http'); doesn't do what you want (or what you think it does).
You could do var $http = angular.element(document).injector().get('$http'); (or probably better use $document there), but imo you should use the $injector-service normally: you can just add $injector to your dependencies/inject statement and inside your interceptor use that to retrieve the service you want like: var $http = $injector.get("$http");
With that your interceptor will intercept the $http-calls it makes.

Doing HTTP request on client-side using AngularJS

After successfully finishing this tutorial, I started building my app routes to handle the creation of some dummy models in the database, which works just fine when I request them through Postman app (using the follwing URL: https://lab4-roger13.c9users.io:8080/api/nerds).
The next step, was to create a service in AngularJS to allow the user to request those same informations at the client side. At the end of the tutorial I was left with this:
angular.module('NerdService', []).factory('Nerd', ['$http', function($http) {
return {
// call to get all nerds
get : function() {
return $http.get('/api/nerds');
},
a : 2,
// these will work when more API routes are defined on the Node side of things
// call to POST and create a new nerd
create : function(nerdData) {
return $http.post('/api/nerds', nerdData);
},
// call to DELETE a nerd
delete : function(id) {
return $http.delete('/api/nerds/' + id);
}
}
}]);
And this is the module that links all my services and routes:
angular.module('sampleApp',
['ngRoute', 'appRoutes', 'MainCtrl', 'NerdCtrl', 'NerdService'])
.controller('nerdDB', ['$scope', 'Nerd', function($scope, Nerd) {
$scope.a = Nerd.a;
}]);
Here is a sample of a backend route I'm trying to access:
module.exports = function(app) {
// get all nerds in the database (accessed at GET https://lab4-roger13.c9users.io:8080/api/nerds)
app.get('/api/nerds', function(req, res) {
// use mongoose to get all nerds in the database
Nerd.find(function(err, nerds) {
// if there is an error retrieving, send the error.
// nothing after res.send(err) will execute
if (err)
res.send(err);
res.json(nerds); // return all nerds in JSON format
});
});
As you can imagine, I can access the a property of the service at the html by using the {{a}} notation, which displays 2. But when I try the same with the get property, nothing shows up.
I'm not sure, is the URL the tutorial provides at the $http.get wrong or am I missing a step to do and access the GET response?
(If I missed any relevant code, they are the same as the ones that can be found at the tutorial link)
Nerd.get() is a function that returns a promise from the $http service. If you want to show it's result in your view, you can bind the results to your view like so:
.controller('nerdDB', ['$scope', 'Nerd', function($scope, Nerd) {
Nerd.get().then(function(nerds) {
$scope.nerds = nerds;
});
}]);
loved this post I had some problem using factory and found solution here
nerds controller
angular.module('NerdCtrl', []).controller('NerdController', function($scope, Nerd) {
$scope.tagline = 'bla bla';
$scope.nerds = {};
Nerd.getNerd()
.then(function (components) {
$scope.nerds = components;
}, function (error) {
console.error(error);
});
});
As service
angular.module('NerdService', []).factory('Nerd', function ($q, $http) {
return {
getNerd: function () {
var deferred = $q.defer(),
httpPromise = $http.get('/api/nerds');
httpPromise.success(function (components) {
deferred.resolve(components);
})
.error(function (error) {
console.error('Error: ' + error);
});
return deferred.promise;
}
};
});
In NerdHTLM using controller to loop records
<table ng-controller="NerdController" >
<tbody>
<tr ng-repeat="nerd in nerds">
<td>{{nerd.name}}</td>
<td>{{nerd.type}}</td>
</tr>
</tbody>
</table>

Inject service in app.config

I want to inject a service into app.config, so that data can be retrieved before the controller is called. I tried it like this:
Service:
app.service('dbService', function() {
return {
getData: function($q, $http) {
var defer = $q.defer();
$http.get('db.php/score/getData').success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
};
});
Config:
app.config(function ($routeProvider, dbService) {
$routeProvider
.when('/',
{
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
data: dbService.getData(),
}
})
});
But I get this error:
Error: Unknown provider: dbService from EditorApp
How to correct setup and inject this service?
Set up your service as a custom AngularJS Provider
Despite what the Accepted answer says, you actually CAN do what you were intending to do, but you need to set it up as a configurable provider, so that it's available as a service during the configuration phase.. First, change your Service to a provider as shown below. The key difference here is that after setting the value of defer, you set the defer.promise property to the promise object returned by $http.get:
Provider Service: (provider: service recipe)
app.provider('dbService', function dbServiceProvider() {
//the provider recipe for services require you specify a $get function
this.$get= ['dbhost',function dbServiceFactory(dbhost){
// return the factory as a provider
// that is available during the configuration phase
return new DbService(dbhost);
}]
});
function DbService(dbhost){
var status;
this.setUrl = function(url){
dbhost = url;
}
this.getData = function($http) {
return $http.get(dbhost+'db.php/score/getData')
.success(function(data){
// handle any special stuff here, I would suggest the following:
status = 'ok';
status.data = data;
})
.error(function(message){
status = 'error';
status.message = message;
})
.then(function(){
// now we return an object with data or information about error
// for special handling inside your application configuration
return status;
})
}
}
Now, you have a configurable custom Provider, you just need to inject it. Key difference here being the missing "Provider on your injectable".
config:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
dbData: function(DbService, $http) {
/*
*dbServiceProvider returns a dbService instance to your app whenever
* needed, and this instance is setup internally with a promise,
* so you don't need to worry about $q and all that
*/
return DbService('http://dbhost.com').getData();
}
}
})
});
use resolved data in your appCtrl
app.controller('appCtrl',function(dbData, DbService){
$scope.dbData = dbData;
// You can also create and use another instance of the dbService here...
// to do whatever you programmed it to do, by adding functions inside the
// constructor DbService(), the following assumes you added
// a rmUser(userObj) function in the factory
$scope.removeDbUser = function(user){
DbService.rmUser(user);
}
})
Possible Alternatives
The following alternative is a similar approach, but allows definition to occur within the .config, encapsulating the service to within the specific module in the context of your app. Choose the method that right for you. Also see below for notes on a 3rd alternative and helpful links to help you get the hang of all these things
app.config(function($routeProvider, $provide) {
$provide.service('dbService',function(){})
//set up your service inside the module's config.
$routeProvider
.when('/', {
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
data:
}
})
});
A few helpful Resources
John Lindquist has an excellent 5 minute explanation and demonstration of this at egghead.io, and it's one of the free lessons! I basically modified his demonstration by making it $http specific in the context of this request
View the AngularJS Developer guide on Providers
There is also an excellent explanation about factory/service/provider at clevertech.biz.
The provider gives you a bit more configuration over the .service method, which makes it better as an application level provider, but you could also encapsulate this within the config object itself by injecting $provide into config like so:
Alex provided the correct reason for not being able to do what you're trying to do, so +1. But you are encountering this issue because you're not quite using resolves how they're designed.
resolve takes either the string of a service or a function returning a value to be injected. Since you're doing the latter, you need to pass in an actual function:
resolve: {
data: function (dbService) {
return dbService.getData();
}
}
When the framework goes to resolve data, it will inject the dbService into the function so you can freely use it. You don't need to inject into the config block at all to accomplish this.
Bon appetit!
Short answer: you can't. AngularJS won't allow you to inject services into the config because it can't be sure they have been loaded correctly.
See this question and answer:
AngularJS dependency injection of value inside of module.config
A module is a collection of configuration and run blocks which get
applied to the application during the bootstrap process. In its
simplest form the module consist of collection of two kinds of blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants
can be injected into configuration blocks. This is to prevent
accidental instantiation of services before they have been fully
configured.
I don't think you're supposed to be able to do this, but I have successfully injected a service into a config block. (AngularJS v1.0.7)
angular.module('dogmaService', [])
.factory('dogmaCacheBuster', [
function() {
return function(path) {
return path + '?_=' + Date.now();
};
}
]);
angular.module('touch', [
'dogmaForm',
'dogmaValidate',
'dogmaPresentation',
'dogmaController',
'dogmaService',
])
.config([
'$routeProvider',
'dogmaCacheBusterProvider',
function($routeProvider, cacheBuster) {
var bust = cacheBuster.$get[0]();
$routeProvider
.when('/', {
templateUrl: bust('touch/customer'),
controller: 'CustomerCtrl'
})
.when('/screen2', {
templateUrl: bust('touch/screen2'),
controller: 'Screen2Ctrl'
})
.otherwise({
redirectTo: bust('/')
});
}
]);
angular.module('dogmaController', [])
.controller('CustomerCtrl', [
'$scope',
'$http',
'$location',
'dogmaCacheBuster',
function($scope, $http, $location, cacheBuster) {
$scope.submit = function() {
$.ajax({
url: cacheBuster('/customers'), //server script to process data
type: 'POST',
//Ajax events
// Form data
data: formData,
//Options to tell JQuery not to process data or worry about content-type
cache: false,
contentType: false,
processData: false,
success: function() {
$location
.path('/screen2');
$scope.$$phase || $scope.$apply();
}
});
};
}
]);
You can use $inject service to inject a service in you config
app.config(function($provide){
$provide.decorator("$exceptionHandler", function($delegate, $injector){
return function(exception, cause){
var $rootScope = $injector.get("$rootScope");
$rootScope.addError({message:"Exception", reason:exception});
$delegate(exception, cause);
};
});
});
Source: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx
** Explicitly request services from other modules using angular.injector **
Just to elaborate on kim3er's answer, you can provide services, factories, etc without changing them to providers, as long as they are included in other modules...
However, I'm not sure if the *Provider (which is made internally by angular after it processes a service, or factory) will always be available (it may depend on what else loaded first), as angular lazily loads modules.
Note that if you want to re-inject the values that they should be treated as constants.
Here's a more explicit, and probably more reliable way to do it + a working plunker
var base = angular.module('myAppBaseModule', [])
base.factory('Foo', function() {
console.log("Foo");
var Foo = function(name) { this.name = name; };
Foo.prototype.hello = function() {
return "Hello from factory instance " + this.name;
}
return Foo;
})
base.service('serviceFoo', function() {
this.hello = function() {
return "Service says hello";
}
return this;
});
var app = angular.module('appModule', []);
app.config(function($provide) {
var base = angular.injector(['myAppBaseModule']);
$provide.constant('Foo', base.get('Foo'));
$provide.constant('serviceFoo', base.get('serviceFoo'));
});
app.controller('appCtrl', function($scope, Foo, serviceFoo) {
$scope.appHello = (new Foo("app")).hello();
$scope.serviceHello = serviceFoo.hello();
});
Using $injector to call service methods in config
I had a similar issue and resolved it by using the $injector service as shown above. I tried injecting the service directly but ended up with a circular dependency on $http. The service displays a modal with the error and I am using ui-bootstrap modal which also has a dependency on $https.
$httpProvider.interceptors.push(function($injector) {
return {
"responseError": function(response) {
console.log("Error Response status: " + response.status);
if (response.status === 0) {
var myService= $injector.get("myService");
myService.showError("An unexpected error occurred. Please refresh the page.")
}
}
}
A solution very easy to do it
Note : it's only for an asynchrone call, because service isn't initialized on config execution.
You can use run() method. Example :
Your service is called "MyService"
You want to use it for an asynchrone execution on a provider "MyProvider"
Your code :
(function () { //To isolate code TO NEVER HAVE A GLOBAL VARIABLE!
//Store your service into an internal variable
//It's an internal variable because you have wrapped this code with a (function () { --- })();
var theServiceToInject = null;
//Declare your application
var myApp = angular.module("MyApplication", []);
//Set configuration
myApp.config(['MyProvider', function (MyProvider) {
MyProvider.callMyMethod(function () {
theServiceToInject.methodOnService();
});
}]);
//When application is initialized inject your service
myApp.run(['MyService', function (MyService) {
theServiceToInject = MyService;
}]);
});
Well, I struggled a little with this one, but I actually did it.
I don't know if the answers are outdated because of some change in angular, but you can do it this way:
This is your service:
.factory('beerRetrievalService', function ($http, $q, $log) {
return {
getRandomBeer: function() {
var deferred = $q.defer();
var beer = {};
$http.post('beer-detail', {})
.then(function(response) {
beer.beerDetail = response.data;
},
function(err) {
$log.error('Error getting random beer', err);
deferred.reject({});
});
return deferred.promise;
}
};
});
And this is the config
.when('/beer-detail', {
templateUrl : '/beer-detail',
controller : 'productDetailController',
resolve: {
beer: function(beerRetrievalService) {
return beerRetrievalService.getRandomBeer();
}
}
})
Easiest way:
$injector = angular.element(document.body).injector()
Then use that to run invoke() or get()

AngularJS: need to fire event every time an ajax call is started

I am trying to fire an event on $rootScope every time an ajax call is started.
var App = angular.module('MyApp');
App.config(function ($httpProvider) {
//add a transformRequest to preprocess request
$httpProvider.defaults.transformRequest.push(function () {
//resolving $rootScope manually since it's not possible to resolve instances in config blocks
var $rootScope = angular.injector(['ng']).get('$rootScope');
$rootScope.$broadcast('httpCallStarted');
var $log = angular.injector(['ng']).get('$log');
$log.log('httpCallStarted');
});
});
The event 'httpCallStarted' it's not being fired. I suspect that it's not correct to use $rootScope or any other instance service in config blocks. If so, how can I get an event everytime an http call is starting, without having to pass a config object in every time I am making a call?
Thanks in advance
You could always wrap $http in a service. Since services are only set up one time, you could just have the service factory set up the events for you. It feels a little hackish to me, honestly, but it's a good work around, since Angular doesn't have a global way to do this yet, unless something was added in 1.0.3 that I'm not aware of.
Here's a plunker of it working
And here's the code:
app.factory('httpPreConfig', ['$http', '$rootScope', function($http, $rootScope) {
$http.defaults.transformRequest.push(function (data) {
$rootScope.$broadcast('httpCallStarted');
return data;
});
$http.defaults.transformResponse.push(function(data){
$rootScope.$broadcast('httpCallStopped');
return data;
})
return $http;
}]);
app.controller('MainCtrl', function($scope, httpPreConfig) {
$scope.status = [];
$scope.$on('httpCallStarted', function(e) {
$scope.status.push('started');
});
$scope.$on('httpCallStopped', function(e) {
$scope.status.push('stopped');
});
$scope.sendGet = function (){
httpPreConfig.get('test.json');
};
});
Cagatay is right. Better use $http interceptors:
app.config(function ($httpProvider, $provide) {
$provide.factory('httpInterceptor', function ($q, $rootScope) {
return {
'request': function (config) {
// intercept and change config: e.g. change the URL
// config.url += '?nocache=' + (new Date()).getTime();
// broadcasting 'httpRequest' event
$rootScope.$broadcast('httpRequest', config);
return config || $q.when(config);
},
'response': function (response) {
// we can intercept and change response here...
// broadcasting 'httpResponse' event
$rootScope.$broadcast('httpResponse', response);
return response || $q.when(response);
},
'requestError': function (rejection) {
// broadcasting 'httpRequestError' event
$rootScope.$broadcast('httpRequestError', rejection);
return $q.reject(rejection);
},
'responseError': function (rejection) {
// broadcasting 'httpResponseError' event
$rootScope.$broadcast('httpResponseError', rejection);
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('httpInterceptor');
});
I think interceptors works for versions after 1.1.x.
There was responseInterceptors before that version.
I have verified that this code will work as you expect. As I mentioned above, you are not retrieving the injector that you think you are and need to retrieve the one being used for your app.
discussionApp.config(function($httpProvider) {
$httpProvider.defaults.transformRequest.push(function(data) {
var $injector, $log, $rootScope;
$injector = angular.element('#someid').injector();
$rootScope = $injector.get('$rootScope');
$rootScope.$broadcast('httpCallStarted');
$log = $injector.get('$log');
$log.log('httpCallStarted');
return data;
});
});
The best way to do this is to use an http interceptor. Check this link

Resources