I have an app with a service which wraps my API calls:
var ConcernService = {
...
get: function (items_url, objId) {
var defer = $q.defer();
$http({method: 'GET',
url: api_url + items_url + objId}).
success(function (data, status, headers, config) {
defer.resolve(data);
}).error(function (data, status, headers, config) {
console.log('ConcernService.get status',status);
defer.reject(status);
});
return defer.promise;
},
and I'm using UI-Router to transition between states:
concernsApp
.config( function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/404/");
$stateProvider.state('project', {
url: '/project/:projectId/',
resolve: {
project: function ($stateParams, ConcernService) {
return ConcernService.get('projects/', $stateParams.projectId);
},
},
views: {
...
}
});
I'm moving from using the normal AngularJS router and I'm having difficulty understanding how to implement 404s. I can see the ConcernService throwing the console.log status as rejected, but how do I catch this in the state router?
The otherwise() rule is only invoked when no other route matches. What you really want is to intercept the $stateChangeError event, which is what gets fired when something goes wrong in a state transition (for example, a resolve failing). You can read more about that in the state change event docs.
The simplest implementation for what you're trying to do would be something like this:
$rootScope.$on('$stateChangeError', function(event) {
$state.go('404');
});
Also, since $http itself is built on promises (which resolve resolves), your ConcernService method can be simplified down to a one-liner (I realize you expanded it for debugging purposes, but you could have just as easily chained it, just FYI):
var ConcernService = {
get: function (items_url, objId) {
return $http.get(api_url + items_url + objId);
}
}
I differ between two 404 states:
Server:
show 404 page depending on server response HTTP Code 404
important to define no URL, so that user stays on URL where the error happened
Client:
URL is not found by angular ui router (none of defined URLs)
Code for Angular UI-Router state:
$stateProvider
.state('404server', {
templateUrl: '/views/layouts/404.html'
})
.state('404client', {
url: '*path',
templateUrl: '/views/layouts/404.html'
});
Code in $httpProvider interceptor:
if(response.status === 404) {
$injector.get('$state').go('404server');
}
And why I used $injector instead of $state is explained here.
You can also try something like this and see if it works for you. You may need to adjust to your needs:
.state('otherwise', {
abstract: true,
templateUrl: 'views/404.html'
})
.state('otherwise.404', {
url: '*path',
templateUrl: 'views/404.html'
})
The $urlRouterProvider only works like a $watch to $location and if the actual URL matches one of the rules defined in the .config() function then it will redirect to the specified route.
Here's what I recommend, define "/404/" as a state:
$stateProvider.state('404', {
url:'/404/',
views:{
...
}
});
And inside the reject() function move to 404 state
if(status == '404'){
$state.transitionTo('404');
}
You will have to add ui-router as dependency of the project module and use the $state provider in your controller in order to be able to use $state.transitionTo()
Here's some info: https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statetransitiontoto-toparams--options
I managed to handle 404 without using $urlRoutProvider since I'm only using states by testing $state.transistion:
angular.module("app", []).run(["$state", "$rootScope", function($state, $rootScope) => {
$rootScope.$on("$locationChangeSuccess", function() {
if (!$state.transition) {
$state.go("404");
}
});
}]);
$urlRouterProvider.otherwise('/page-not-found');
.state('error', {
url: "/page-not-found",
templateUrl: "templates/error.html",
controller: "errorController"
})
Will handle your page not found problem.
If you want to raise 404 found purposefully use the state or url. We have created a separate controller just if you want to perform any operations.
Related
I'm using angular $stateProvider to allow routes throughout my app. I have an index.html and I am changing the html content inside using the following...
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/app');
$stateProvider
.state('app', {
url: '/app',
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('app.accounts', {
url: '/accounts',
templateUrl: 'templates/accounts.html'
})
});
The first route works, when you open the app template/menu.html is put inside index.html. The problem occur when I try to change the state.
At the moment, I am implementing an API in my app, what the API does is irrelevant, but on success I want the API to change the state to app.accounts, see below...
app.controller('AppCtrl', function($scope, $ionicModal, $timeout) {
$scope.create = function() {
var linkHandler = Plaid.create({
env: 'tartan',
clientName: 'Example Project',
key: 'test_key',
product: 'connect',
//On success I want to change the state
onSuccess: function(token) {
window.location = '/app/accounts';
},
});
linkHandler.open();
}
});
Above, on success, I attempt changing the state, but when window.location is called, I get the error...
net::ERR_FILE_NOT_FOUND (file:///app/accounts)
I am not sure why I get this error because the first route works fine. Any idea how I can fix this?
Your path is wrong. That's what the error is saying. It can't find that route.
Try $window.location.href = "/#/app/accounts";
Remember to inject $window in the controller.
However a better way would be to inject the $state service and use it to navigate:
$state.go('app.accounts')
I am using $http in angular for ajax calls and using ui.router for routing.
Routes
.state("/dashboard.inactive", {
url: "/inactive",
templateUrl: "angular/templates/dashboard/inactive.html",
controller: "dashboardCtrl"
})
.state("/dashboard.drafts", {
url: "/drafts",
templateUrl: "angular/templates/dashboard/drafts.html",
controller: "dashboardCtrl"
});
So the below code works if it is for a single URL.
Controller
app.controller('dashboardCtrl', function ($scope, DashboardFactory){
DashboardFactory.listings(function(DashboardFactory) {
$scope.results = DashboardFactory;
});
});
Below factory is fetching only from drafts.json resource. So when the URL changes to inactive I want it to fetch from inactive.json and active.json respectively.
Factory
app.factory('DashboardFactory', function($http){
return {
listings: function(callback){
$http.get('drafts.json').success(callback);
}
};
});
In short I need to send requests to any one of the below 3 URLs based on the URL
1) '/drafts.json'
2) '/inactive.json'
3) '/active.json'
I can create a different controllers for each active, inactive and drafts and make it fetch as expected. But is there any better way to do this??
You could use the $state service of ui route in order to tell which state your are in.
Just inject $state to your service and then use $state.current in order to access the current state config.
app.factory('DashboardFactory',
function($http, $state){
return {
listings: function(callback){
var currentView = $state.current.url.replace('/', '');
$http.get(currentView + '.json').success(callback);
}
};
});
A better solution would be to either use the params property of the state config or add some custom property like:
.state("/dashboard.inactive", {
url: "/inactive",
templateUrl: "angular/templates/dashboard/inactive.html",
controller: "dashboardCtrl",
params: {
json: 'inactive.json'
}
})
.state("/dashboard.drafts", {
url: "/drafts",
templateUrl: "angular/templates/dashboard/drafts.html",
controller: "dashboardCtrl",
params: {
json: 'drafts.json'
}
});
It is described in the documentation.
I am attempting to lazy load a controller and template in my UI-Router router.js file, but am having difficulty with the template.
The controller loads properly, but after that is loaded, we must load the template and this is where things go wrong.
After ocLazyLoad loads the controller, we resolve an Angular promise which is also included in the templateProvider. The issue is instead of returning the promise (templateDeferred.promise) after the file is done loading, the promise is returned as an object.
.state('log_in', {
url: '/log-in',
controller: 'controllerJsFile',
templateProvider: function($q, $http) {
var templateDeferred = $q.defer();
lazyDeferred.promise.then(function(templateUrl) {
$http.get(templateUrl)
.success(function(data, status, headers, config) {
templateDeferred.resolve(data);
}).
error(function(data, status, headers, config) {
templateDeferred.resolve(data);
});
});
return templateDeferred.promise;
},
resolve: {
load: function($templateCache, $ocLazyLoad, $q) {
lazyDeferred = $q.defer();
var lazyLoader = $ocLazyLoad.load ({
files: ['src/controllerJsFile']
}).then(function() {
return lazyDeferred.resolve('src/htmlTemplateFile');
});
return lazyLoader;
}
},
data: {
public: true
}
})
Ok, thanks for the responses, but I have figured it out.
.state('log_in', {
url: '/log-in',
controller: 'controllerJsFile',
templateProvider: function() { return lazyDeferred.promise; },
resolve: {
load: function($templateCache, $ocLazyLoad, $q, $http) {
lazyDeferred = $q.defer();
return $ocLazyLoad.load ({
name: 'app.logIn',
files: ['src/controllerJsFile.js']
}).then(function() {
return $http.get('src/htmlTemplateFile.tpl.html')
.success(function(data, status, headers, config) {
return lazyDeferred.resolve(data);
}).
error(function(data, status, headers, config) {
return lazyDeferred.resolve(data);
});
});
}
},
data: {
public: true
}
})
So, after some more reading, I realized I had an issue with my promises. We create one called lazyDeferred which is the one to be returned to templateProvider and is declared as a global variable. templateProvider waits for the promise to be fulfilled.
After we load our controller, we create an XHR/ $http request to retrieve the template file. $http.get is a promise so we can return that, $ocLazyLoad.load also is a promise so we can return that as well. Finally, we just need to resolve the lazyDeferred one and that I think balloons through the promises and resolves all of them.
I apologize if this was not very clear, I'm not 100% sure of how this works.
In case you'd like to lazily load the controller, I would suggest follow these detailed answers:
requirejs with angular - not resolving controller dependency with nested route
angular-ui-router with requirejs, lazy loading of controller
In case we need to load dynamically the HTML template, it is much more easier. There is an example from this Q & A
Trying to Dynamically set a templateUrl in controller based on constant
(the working plunker)
$stateProvider
.state('home', {
url: '/home',
//templateUrl: 'index5templateA.html', (THIS WORKS)
templateProvider: function(CONFIG, $http, $templateCache) {
console.log('in templateUrl ' + CONFIG.codeCampType);
var templateName = 'index5templateB.html';
if (CONFIG.codeCampType === "svcc") {
templateName = 'index5templateA.html';
}
var tpl = $templateCache.get(templateName);
if(tpl){
return tpl;
}
return $http
.get(templateName)
.then(function(response){
tpl = response.data
$templateCache.put(templateName, tpl);
return tpl;
});
},
You can check these as well:
Angular UI Router: decide child state template on the basis of parent resolved object
Angular and UI-Router, how to set a dynamic templateUrl
I am continuing to have issues where my templates and directives are throwing errors because they are trying to be $compiled before the data is actually set. This is because it takes time for the API response to get back.
Therefore, I am trying to convert my API call to work in the resolve property of my route, however I cannot figure out how to do it correctly. Here is what I have:
My State Provider w/ resolve property
$stateProvider
.state('dashboard', {
url: '/',
controller: 'LandingController',
resolve: {
data: ['API', function(API){
return API.Backups.getAll(function (data) {
return data.result;
});
}]
}
})
My controller
app.controller('LandingController', ['$scope', 'API', function ($scope, API, data) {
$scope.data = data;
......
I am using an Angular service that provides a $resource in order to get the API data, however something is not working still because my data parameter in the controller is still undefined.
I figured out what I needed. I just needed to return the $resource.$promise instead of just the $resource. After doing that, everything worked great!
Solution
$stateProvider
.state('dashboard', {
url: '/',
controller: 'LandingController',
resolve: {
res: ['API', function(API){
return API.Backups.getAll(function (data) {
return data.result;
}).$promise;
}]
}
})
Without the code for API.Backups.getAll() I can't tell, but the resolve function needs to return a "promise" which can be provided by the Angular $q:
resolve: ['$q', 'API', function($q, API) {
// 1. $q provides the deferred which satisfies the "promise" API
var deferred = $q.defer();
// 2. call your API
API.Backups.getAll(function (data) {
// 4. resolve the promise with data that was returned
deferred.resolve(data.result);
});
// 3. return the promise
return deferred.promise;
}]
The numbered comments show the order things will occur. Angular will not instantiate your controller until resolve() is called on the promise (deferred).
see: https://docs.angularjs.org/api/ng/service/$q
I am new to AngularJs. I have a single page app with routes configured having a controller and a view. The view get loaded inside the <ng-view></ng-view> element of the index.html page. Inside the controller I am making a http call to get the data and binding the data to the $scope. For success scenarios this works fine but if there is an error how do I plug in another view instead of the default view configured inside the angular route. PLease let me know.
To implement common scenario for processing ajax errors you can implement custom request interceptor and redirect user to error page (or login page) according to error status:
myApp.factory('httpErrorResponseInterceptor', ['$q', '$location',
function($q, $location) {
return {
response: function(responseData) {
return responseData;
},
responseError: function error(response) {
switch (response.status) {
case 401:
$location.path('/login');
break;
case 404:
$location.path('/404');
break;
default:
$location.path('/error');
}
return $q.reject(response);
}
};
}
]);
//Http Intercpetor to check auth failures for xhr requests
myApp.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('httpErrorResponseInterceptor');
}
]);
Plunker here
Use $location.url() to redirect to a 404.html when error is occured
$http.get(url,[params])
.success(function(data, status, headers, config){
// bind your data to scope
})
.error(function(data, status, headers, config) {
$location.url('/404');
});
Then configure your $routeProvider
$routeProvider
.when('/404', {
templateUrl: '404.html',
controller: 'Four04Controller'
})
you could use: https://github.com/angular-ui/ui-router
In case of error, you can trigger a "error" state.
I had the same problem some weeks ago and I have resolved in this way
If you use $stateProvider instead of $routeProvider you can do like this:
function routerConfig($stateProvider, $urlRouterProvider) {
$stateProvider
.state('404', {
url: '/404',
templateUrl: '404.html'
})
.state('home', {
url: '/',
templateUrl: 'home.html'
});
$urlRouterProvider.otherwise('/404');
}
Pay attention to $urlRouterProvider.otherwise(url), which is the function that gets called when the provider doesn't find the requested url, so it automatically redirect to the url provided in this function.