Error handling when data is not resolved in Angular UI router - angularjs

I am using angular-ui-router's resolve to get data from server before moving a state. I want to inform user if the request has failed. I have service that will response error the request has failed. My question is how can I detect the failure in ui-router resolve and trigger some Modal service like pop up.
I have read some related posts online, but I am still confused how to make it happen. Thanks in advanced!
Config and Service:
angular.module('myApp',['ui.router', 'ngAnimate', 'ui.bootstrap'])
.config(function ($stateProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$stateProvider
.state('customer', {
url: '/customer',
templateUrl: '/customer.html',
controller: 'CustomerCtrl',
resolve: {
/*
* How to inject CustomerService here
* How to catch the server error
* How to trigger a popup
*/
data: cusomter_data
}
});
})
.service('CustomerService', function($http, $q) {
return ({
getGeneral: getGeneral
});
function getGeneral(customer_id) {
var request = $http({
method: "get",
url: '/customer',
params: {
customer_id: customer_id
}
});
return (request.then( handleSuccess, handleError));
}
function handleError (response){
if (!angular.isObject(response.data) || !response.data.message) {
return($q.reject("Failed to retrieve customer information."));
}
return($q.reject(response.data.message));
}
function handleSuccess(response) {
return (response.data);
}
});

After some research, I found out a solution by creating a errorModal service and inject it to resolve. Here is my solution.
$stateProvider
.state('customer', {
url: '/customer',
templateUrl: '/customer.html',
controller: 'CustomerCtrl',
resolve: {
data: function(CustomerService, SelectedCustomerService, errorModalService) {
var shared = SelectedCustomerService;
return CustomerService.getData(shared.customerid).then(
function (data) { return data; },
function(error) {
var modalOptions = {
closeButtonText: 'Close',
headerText: 'Customer Error',
bodyText: error
};
errorModalService.showModal({}, modalOptions);
}
);
}
}
});

Related

AngularUI Route Resolve Issue when Rejecting Promise

I have included my code below. Basically, when I am loading my view, I am using resolve to get some data. In my service, if my promise is rejected - on error - the resolve gets infinitely called. Is there a better way I should be performing this?
(function () {
function AppService($q, $http, $log, $timeout, pageOptionsModel) {
return {
getPageOptions: function () {
var deferred = $q.defer();
var pageOptions = pageOptionsModel.getPageOptions();
if (pageOptions === null) {
$http.get("api/HomeApi/GetPageOptions")
.success(function (response) {
deferred.resolve(response);
$log.info("Successfully retriedved page options from service.");
})
.error(function (response) {
deferred.reject("Error");
$log.error("Errored while retrieving page options from service.");
});
}
else {
deferred.resolve(pageOptions);
}
return deferred.promise;
}
}
};
function AppConfig($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("line");
// Now set up the states
$stateProvider
.state('line', {
url: "/line",
templateUrl: "app/line/lineTemplate.html",
controller: "lineController",
controllerAs: "line",
resolve: {
pageOptions: function (appService) {
return appService.getPageOptions();
}
}
});
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
};
angular.module("app", ["ui.router", "ui.bootstrap", "app.line", "app.modal"])
.config(AppConfig)
.factory("appService", AppService);
})();
Here is my Line Controller which never initializes if my promise is rejected.
(function () {
function LineController($scope, pageOptions) {
var self = this;
// INITIALIZE
self.pageOptions = pageOptions;
};
angular.module("app.line")
.controller("lineController", LineController);
})();

Why won't $http set a default header?

$http.post('http://localhost:7001/v1/sessions', {
data: {
username: $scope.user.username,
password: $scope.user.password,
type: 'sessions'
}
})
.then(function(response) {
if(response.data.data.token) {
$http.defaults.headers.common.Authorization = response.data.data.token;
$state.go('app.dashboard');
} else {
$scope.authError = response;
}
}, function(x) {
$scope.authError = 'Server Error';
});
I can confirm that the if condition gets called and a response.data.data.token is present.
It goes to the app.dashboard state but is intercepted by my ui-router:
$stateProvider.state('app', {
abstract: true,
url: '/app',
templateUrl: 'tpl/app.html',
resolve: {
current_user: ['$http', function($http) {
return $http.get('http://localhost:7001/v1/users/4/entities');
}]
}
})
That call, however, does not have anything set in the header. I thought that $http.defaults would set a default value in the header. What am I doing incorrectly?
You must set the default headers in the config method and not in your service.
Example:
myApp.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common['Content-Type'] = 'application/json; charset=utf-8';
}]);
Only in config you can configure the httpProvider. If you try to do that inside your service, it won't affect the $httpProvider service at all.
EDIT:
You must make use Interceptors in this scenario.
For purposes of global error handling, authentication, or any kind of
synchronous or asynchronous pre-processing of request or
postprocessing of responses, it is desirable to be able to intercept
requests before they are handed to the server and responses before
they are handed over to the application code that initiated these
requests.
Refer Angular Docs Interceptor section
Just some sample code:
app.service('APIInterceptor', function($rootScope, UserService) {
var service = this;
service.request = function(config) {
// check if the token is available. Once the token is available get it here from the UserService.
var access_token = UserService.getToken() || "unauthorized";
if (access_token) {
config.headers.authorization = access_token;
}
return config;
};
service.responseError = function(response) {
return response;
};
})
In your config
$httpProvider.interceptors.push('APIInterceptor');
I would prefered you to one service to use sharable data.
Code
app.service(dataService, function(){
this.data = {}
this.getData = function(){
return data;
};
this.setTokenData = function(token){
data.token = token;
}
});
Now your code would be while setting token you could use dataService
if(response.data.data.token) {
dataService.setTokenData(response.data.data.token);
$http.defaults.headers.common.Authorization = dataService.data.token; //dataService.getData().token;
$state.go('app.dashboard');
} else {
$scope.authError = response;
}
Then from service resolve you could use
$stateProvider.state('app', {
abstract: true,
url: '/app',
templateUrl: 'tpl/app.html',
resolve: {
current_user: ['$http', 'dataService', function($http, dataService) {
$http.defaults.headers.common.Authorization = dataService.getData().token;
return $http.get('http://localhost:7001/v1/users/4/entities');
}]
}
})

How to redirect with $state.go

I'm trying to redirect my user to Dashboard after Third Party Login. But when success callback is fired, the application still on Login Page, nothing happened. If I refresh the browser my interceptor catch already login and change to Dashboard... My Login Controller looks like this:
ThirdParty.login(function(result){
callbackSUCCESS(result);
},function(){});
function callbackSUCCESS(result){
AuthenticationService.login(result).then(
callbackServerSUCCESS(), function(reject) {
callbackServerERROR(reject);
});
}
function callbackServerSUCCESS() {
$scope.$apply(function() {
$state.go('dashboard');
});
}
My route in app.js
$stateProvider
.state('dashboard', {
url: '/dashboard',
views: {
'': {
templateUrl: 'views/dashboard/dashboard.html',
controller: 'DashboardCtrl'
}
}
});
My Header Controller
.controller('HeaderCtrl', ['$scope', 'AuthenticationService', '$state',
function($scope, AuthenticationService, $state) {
$scope.logout = function() {
AuthenticationService.logout().then(callbackServer(), callbackServer());
};
function callbackServer() {
$state.go('login');
}
}
]);
Authentication Controller Angular Factory
var headersConfig = {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
};
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post('/api/users/sign_in', sanitizeCredentials(credentials), {
headers: headersConfig,
timeout: deferred.promise
}).then(function(result) {
if (result.status === 200) {
UserSessionService.cacheSession(result.data);
deferred.resolve();
} else {
deferred.reject();
}
}, function(reject) {
UserSessionService.clean();
deferred.reject(reject);
});
$timeout(function() {
deferred.resolve();
}, 15000);
return deferred.promise;
}
};
I can't remember the exact semantics of $state.go, but usually you need to use $scope.$apply in some manner when responding to events Angular isn't aware of, to ensure a digest cycle occurs. I.e. you could try:
ThirdParty.login(function(result) {
$scope.$apply(function() {
$state.go('dashboard');
});
}, function() {});
You need to get a reference to a scope from somewhere, but one shouldn't be hard to find in an Angular app.
Get rid of both () in
then(callbackServer(), callbackServer())

Angular Factor Call not working

I am trying to call the update / put method in the factory which will in turn save the changes on the form to the database via an API call. But I get a console error below. The update function is getting called from the button click fine, but it doesn't call the factory and API from there. What am I missing? Thank you!
I updated my code with the suggestion below but now have this error:
My console error: "Error: [$injector:unpr] http://errors.angularjs.org/1.2.10/$injector/unpr?p0=%24resourceProvider%20%3C-%20%24resource%20%3C-%20memberUpdate
var securityApp = angular.module('securityApp', ['ngRoute']).
config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'PartialPages/members.html',
controller: 'membersController'
})
.when('/memberDetail/:memberID', {
templateUrl: 'PartialPages/memberDetail.html',
controller: 'memberDetailController'
})
.when('/memberEdit', {
templateUrl: 'PartialPages/memberEdit.html',
controller: 'memberEditController'
});
});
securityApp.factory('memberUpdate', function ($resource) {
return $resource('/api/Members/:id', { id: '#id' }, { update: { method: 'PUT' } });
});
securityApp.controller('memberDetailController', function ($scope, $http, $routeParams, memberUpdate) {
var id = $routeParams.memberID;
$http.get('/api/Members/' + $routeParams.memberID).success(function (data) {
$scope.member = data;
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
})
$scope.update = function () {
memberUpdate.update({ id: id }, $scope.member);
};
});
You need to inject memberUpdate into the controller dependencies.
securityApp.controller('memberDetailController', function ($scope, $http, $routeParams, memberUpdate) {
var id = $routeParams.memberID;
$http.get('/api/Members/' + $routeParams.memberID).success(function (data) {
$scope.member = data;
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
})
$scope.update = function () { // you don't need to pass $scope and memberUpdate since they are already available into the scope
memberUpdate.update({ id: id }, $scope.member);
};
});
$resource is in a different module so you need to include it.
var securityApp = angular.module('securityApp', ['ngRoute', 'ngResource']).
her how you install it https://docs.angularjs.org/api/ngResource

AngularJS - $routeProvider and resolve not auto updating the view

I am having trouble getting resolve in $routeProvider to automatically update the view.
I am trying to convert an example (chapter 22) in Adam Freeman's Pro Angular book from Deployd to Express-Mongo. A possible hint is that the automatic refresh works using a local version of Deployd. When I switched to local Express and Mongo backend (after making the id to _id changes), the automatic view update no longer works.
When using Express-Mongo, I have to manually execute $route.reload() function to get show the updated changes from the database. I am new to angular so any hints are appreciated.
This heavy handed Delete works:
$scope.deleteProduct = function (product) {
product.$delete();
$route.reload();
$location.path("/list");
}
The below version Doesn't work. I was under the (probably mistaken) impression that the 'resolve' property in $routeProvider updates the view when the data is returned. With the below code, I get a Type Error: Undefined is not a function
$scope.deleteProduct = function (product) {
product.$delete().then(function () {
$scope.data.products.splice($scope.data.products.indexOf(product), 1);
});
$location.path("/list");
}
Console Log Errors
TypeError: undefined is not a function
at http://localhost:8080/bower_components/angular-resource/angular-resource.js:530:25
at Array.forEach (native)
at forEach (http://localhost:8080/bower_components/angular/angular.js:302:11)
at angular.module.factory.Resource.(anonymous function).$http.then.value.$resolved (http://localhost:8080/bower_components/angular-resource/angular-resource.js:529:17)
at deferred.promise.then.wrappedCallback (http://localhost:8080/bower_components/angular/angular.js:10905:81)
at deferred.promise.then.wrappedCallback (http://localhost:8080/bower_components/angular/angular.js:10905:81)
at http://localhost:8080/bower_components/angular/angular.js:10991:26
at Scope.$get.Scope.$eval (http://localhost:8080/bower_components/angular/angular.js:11906:28)
at Scope.$get.Scope.$digest (http://localhost:8080/bower_components/angular/angular.js:11734:31)
at Scope.ng.config.$provide.decorator.$delegate.__proto__.$digest (<anonymous>:844:31) angular.js:9383
Below is the code for the module
angular.module("exampleApp", ["ngResource", "ngRoute"])
.constant("baseUrl", "http://localhost:8080/api/products/")
.factory("productsResource", function ($resource, baseUrl) {
return $resource(baseUrl + ":id", { id: "#_id" },
{
create: { method: "POST" , isArray: true },
save: { method: "PUT", isArray: true },
delete: {method: "DELETE", isArray: true},
update: {method: "PUT", isArray: true},
query: {method: "GET", isArray: true}
})
})
.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
...
$routeProvider.otherwise({
templateUrl: "/views/tableView.html",
controller: "tableCtrl",
resolve: {
data: function (productsResource) {
return productsResource.query();
}
}
});
})
.controller("defaultCtrl", function ($scope, $location, productsResource, $route) {
$scope.data = {};
$scope.deleteProduct = function (product) {
product.$delete().then(function(products) {
//Success
console.log("Success"); //Don't Get Here
$scope.data.products.splice($scope.data.products.indexOf(product), 1);
}, function(errResponse) {
//Failure (Code Produces a Type Error: Undefined is not a function)
console.log("Error: " + errResponse);
});
$location.path("/list");
}
})
The Table View Controller that is bound to the $routeProvider.otherwise
.controller("tableCtrl", function ($scope, $location, $route, data) {
$scope.data.products = data;
$scope.refreshProducts = function () {
$route.reload();
} })
I think you should redirect when the delete is complete:
$scope.deleteProduct = function (product) {
product.$delete().then(function () {
$scope.data.products.splice($scope.data.products.indexOf(product), 1);
$location.path("/list");
});
}
You have missed } at the and of line with text:
delete: {method: "DELETE", isArray: true

Resources