Following is my $routeProvider code -
$routeProvider
.when("/login", {
template: JST["app/templates/login"],
controller: "LoginController",
})
.when("/dashboard", {
template: JST["app/templates/dashboard"],
controller: "DashboardController",
resolve:{
getUser: function(UserService) {
return UserService.chkUser()
}
}
})
.otherwise({ redirectTo: function() {window.location = "/login";} });
$locationProvider.html5Mode(true);
});
Following is my UserService code -
angular.module("app").factory("UserService", function($http, $q, User) {
return {
chkUser: function() {
var defer = $q.defer();
defer.resolve(false);
return defer.promise;
// Also tried -
// window.location = "/login";
}
};
});
Problem -
Now when I am navigating to http://example.com/dashboard in a browser I was expected to be logged back at "/login" but I am not, let me know what I am wrong about the resolve usage in angularJS.
Well, your resolve work fine and if you inject getUser into DashboardController, it's value should be false. Because you immediately resolve the promise with false value.
Try something like this:
angular.module("app").factory("UserService", function($http, $q, User, $location) {
return {
chkUser: function() {
var defer = $q.defer();
$http(...., function(user){
if(user){
defer.resolve(user);
}else{
defer.reject();
$location.path('/login');
}
});
return defer.promise;
}
};
});
Or create a fiddle reproducing the problem, so we could have a look and touch that one.
Related
I want to verify if the user can access a state before he gets there, if he doesn't have permissions will be redirected to another page.
The problem is that I'm doing a SPA and it verifies the permissions, but it takes a while until the server send the response and the user is redirected, so what happen is that a screen appears for 1 or 2 seconds and then is redirected successfully. Is there anyway to avoid this?
This is the code for the state change:
webApp.run(function ($rootScope, $state, StateService) {
$rootScope.$on('$stateChangeStart', function (event, toState, fromState, toParams) {
StateService.hasAccessTo(toState.name, function(data){
if (data.data != ""){
event.preventDefault();
$state.go(data.data);
}
});
});
});
and the service:
webApp.service('StateService', function($http, $rootScope){
this.hasAccessTo = function(state, callback){
$http.get("state/" + state).then(callback);
}
});
I have also tried with a promise in the $stateChangeStart, but it didn't work.
I read about interceptors, but they work if the user is in another page and access mine, if he is already on the page and type a link manually it doesn't intercepts.
Any modifications or suggestions of new ideas or improvements are welcome!
EDIT
Now I have this:
var hasAccessVerification = ['$q', 'StateService', function ($q, $state, StateService) {
var deferred = $q.defer();
StateService.hasAccessTo(this.name, function (data) {
if (data.data !== '') {
$state.go(data.data);
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}];
$urlRouterProvider.otherwise("/");
$compileProvider.debugInfoEnabled(false);
$stateProvider
.state('welcome',{
url:"/",
views: {
'form-view': {
templateUrl: '/partials/form.html',
controller: 'Controller as ctrl'
},
'#': {
templateUrl: '/partials/welcome.html'
}
},
data: {
requireLogin: false
},
resolve: {
hasAccess: hasAccessVerification
}
})
And it validates, but it doesn't load the template. It doesn't show de views. What might I be doing wrong?
EDIT 2
I forgot to add $state here:
var hasAccessVerification = ['$q', '$state', 'StateService', function ($q, $state, StateService){...}
Consider using the resolve in your state configuration instead of using $stateChangeStart event.
According to the docs:
If any of these dependencies are promises, they will be resolved and
converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
Example:
var hasAccessFooFunction = ['$q', 'StateService', function ($q, StateService) {
var deferred = $q.defer();
StateService.hasAccessTo(this.name, function (data) {
if (data.data !== '') {
$state.go(data.data);
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}];
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
resolve: {
hasAccessFoo: hasAccessFooFunction
}
})
.state('user', {
abstract: true,
url: '/user',
resolve: {
hasAccessFoo: hasAccessFooFunction
},
template: '<ui-view/>'
})
.state('user.create', {
url: '/create',
templateUrl: 'views/user/create.html'
})
.state('user.list', {
url: '/list',
templateUrl: 'views/user/list.html'
})
.state('user.edit', {
url: '/:id',
templateUrl: 'views/user/edit.html'
})
.state('visitors', {
url: '/gram-panchayat',
resolve: {
hasAccessFoo: hasAccessFooFunction
},
templateUrl: 'views/visitor/list.html'
});
And according to the docs https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies resolve are inherited:
New in version 0.2.0
Child states will inherit resolved dependencies from parent state(s),
which they can overwrite. You can then inject resolved dependencies
into the controllers and resolve functions of child states.
But, please note:
The resolve keyword MUST be on the state not the views (in case you
use multiple views).
The best practice is to have interceptor on responseError which checks the response status and acts accordingly:
webApp.config(['$httpProvider' ($httpProvider) {
var interceptor = ['$q', '$rootScope', function ($q, $rootScope) {
return {
request: function (config) {
// can also do something here
// for example, add token header
return config;
},
'responseError': function (rejection) {
if (rejection.status == 401 && rejection.config.url !== '/url/to/login') {
// If we're not on the login page
$rootScope.$broadcast('auth:loginRequired');
}
}
return $q.reject(rejection);
}
}
}];
$httpProvider.interceptors.push(interceptor);
}]);
And handle redirection in run block
webApp.run(['$rootScope', function($rootScope){
$rootScope.$on('auth:loginRequired', function () {
$state.go('loginState');
});
}]);
The good thing is that $state service does not need to deal with permission logic:
$stateProvider
.state('someState', {
url: '/some-state',
templateUrl: '/some-state.html',
resolve: {
dataFromBackend: ['dataService', function (postingService) {
// if the request fails, the user gets redirected
return dataService.getData();
}],
},
controller: function ($scope, dataFromBackend) {
}
})
Notice
With this approach, you do not need StateService, all you need to do is to return proper response statuses from backend. For example, if the user is guest, return 401 status.
I am using ui bootstrap and use tmhDynamicLocaleProvider to get the correct translations for days,month etc in my datepicker.
It works all fine as long as I load the locale documents from online. If I load documents locally (I need to do it locally), it doesn't load the correct translations after minification.
My code looks as follows
app.config(['tmhDynamicLocaleProvider', function(tmhDynamicLocaleProvider) {
tmhDynamicLocaleProvider.localeLocationPattern('app/datepickerLocale/locale.{{locale}}.js');
}])
app.config(....{
$provide.decorator('uibDatepickerDirective', ['$delegate', function($delegate) {
angular.forEach($delegate, function (directive) {
var originalCompile = directive.compile;
var originalLink = directive.link;
if (originalCompile) {
directive.compile = function () {
return function (scope) {
scope.$on('$localeChangeSuccess', function () {
scope.move(0);
});
originalLink.apply(this, arguments);
};
}
}
});
return $delegate;
}]);
})
I resolve it in a state
.state('main', {
url: '/{language:[a-z]{2}}',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
controllerAs: 'mainCtrl',
resolve: {
localeLanguage: ['resolveService', '$stateParams', function(resolveService, $stateParams){
resolveService.resolveLocale($stateParams)
}]
}
})
the service looks as follows
resolveLocale: function(stateParams){
var deferred = $q.defer();
if(stateParams.language){
tmhDynamicLocale.set(stateParams.language);
deferred.resolve(1);
} else {
deferred.resolve(2);
}
return deferred.promise;
}
it all works fine until minification. After that I obtain the error, that
the script was not able to be obtained. (GET error).
I assume that the while invoking the function the script locale.en.js has not been loaded yet. Or am I wrong here?
How can I solve this?
I am working on angularjs app and my config looks like this:
.config(function($stateProvider,$urlRouterProvider, $localStorage){
$stateProvider
.state('Login',{
url:'/login',
templateUrl:'templates/login.html',
controller:'LoginCtrl',
resolve: {
/* if($localStorage.userInfo === null || $localStorage.userInfo === undefined){
}else{
$scope.signInUser();
}*/
}
})
My login.html looks like this:
<form name="loginform"></form>
I want that if $localstorage.userInfo exists, do not show login.html but call $scope.signInUser function else show the form.
How do I do this using resolve of the route? Can I please get some directions?
I have tried easier ways but I ended up with 10 digest cycles reached error so I was adviced to use resolve for the purpose.
My complete route looks like this:
.config(function($stateProvider,$urlRouterProvider, $localStorage){
$stateProvider
.state('Login',{
url:'/login',
templateUrl:'templates/login.html',
controller:'LoginCtrl',
resolve: {
if($localStorage.userInfo === null || $localStorage.userInfo === undefined){
}else{
$scope.signInUser();
}
}
})
.state('Deployment',{
url:'/deployment',
templateUrl:'templates/deployment.html'
})
.state('Bill',{
url:'/bills',
templateUrl:'templates/bills.html'
})
//$urlRouterProvider.otherwise('/login');
$urlRouterProvider.otherwise(function ($injector) {
var $state = $injector.get('$state');
$state.go('Login');
});
How I achieved this
routes.js
app.config(["$routeProvider", "$locationProvider", function($routeProvider, $location) {
$routeProvider.when("/home", angularAMD.route({
templateUrl: "templates/pages/home.html",
controller: "HomeController",
controllerUrl: "app/controllers/home_controller",
resolve: {
weekendTypes: function(WeekendTypes) {
return WeekendTypes.getAll()
}
}
}));
}]);
factory.js
app.factory("WeekendTypes", ["$http", function($http) {
return {
getAll: function() {
var _apiurl = config_data.GENERAL_CONFIG.AJAX_URL + "weekend/getAll",
promise = $http({
method: "GET",
url: _apiurl
}).success(function(data, status, headers, config) {
return data
});
return promise
}
}
}])
You should use something like this
getData: function(){
var deferred = $q.defer();
$timeout(function(){
var data=localStorageService.get('description'),
deferred.resolve(data);
},3000);
return deferred.promise;
}
Had to try it by myself before sharing code.
Remove the resolve function from set local storage value as data like below
state('Login',{
url:'/login',
templateUrl:'templates/login.html',
controller:'LoginCtrl',
data : {userIfno : $localStorage.userInfo}
})
Then make use of .run function like
.run($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(toState) {
if (toState.data.userIfno === '') {
$state.go('Login', {}); //here set proper state
}
});
}
Hope this helps you.
I'm having trouble figuring out how I could pass a parameter to a function that is a part of resolve of ngRoute.
In my case I'm doing stuff with tokens. These tokens are typed, so you cannot use the same token for confirming and email and reseting a password. Here's how my routes are defined:
.when("/confirm/:token", {
controller: "confirmEmailController",
templateUrl: "/app/views/confirmEmail.html",
resolve: {
tokenStatus: getTokenStatus
}
})
.when("/reset/:token", {
controller: "resetPasswordController",
templateUrl: "/app/views/resetPasswordEmail.html",
resolve: {
tokenStatus: getTokenStatus
}
})
Here's the getTokenStatus function that's being called for both of them:
var getTokenStatus = ["$q", "$route", "tokenService", function($q, $route, tokenService)
{
var deferred = $q.defer();
var tokenType = ???? //<-- how do I pass this?
tokenService
.getTokenStatus($route.current.params.token, tokenType)
.success(function(response)
{
deferred.resolve(true);
})
.error(function()
{
deferred.resolve(false);
});
return deferred.promise;
}];
The problem is that in order to avoid code duplication I need to somehow pass the value of the token type, as marked in the code. How could I do that?
I've been messing about with this for the last 2 hours, but can't seem to figure it out.
1. You can try to include token type into route
.when("/:tokenType/:token", {
controller: "confirmEmailController",
templateUrl: "/app/views/confirmEmail.html",
resolve: {
tokenStatus: getTokenStatus
}
})
.when("/:tokenType/:token", {
controller: "resetPasswordController",
templateUrl: "/app/views/resetPasswordEmail.html",
resolve: {
tokenStatus: getTokenStatus
}
})
And then just get it from $route.current.params.tokenType. But it's not clean solution - you should check your URL for validity.
2. You can use function wrapping
$routeProvider.when("/confirm/:token", {
controller: "confirmEmailController",
templateUrl: "/app/views/confirmEmail.html",
resolve: {
tokenStatus: getTokenStatus("confirm")
}
})
.when("/reset/:token", {
controller: "resetPasswordController",
templateUrl: "/app/views/resetPasswordEmail.html",
resolve: {
tokenStatus: getTokenStatus("reset")
}
});
var getTokenStatus = function(tokenType) {
return ["$q", "$route", "tokenService", function($q, $route, tokenService) {
var deferred = $q.defer();
tokenService
.getTokenStatus($route.current.params.token, tokenType)
.success(function(response)
{
deferred.resolve(true);
})
.error(function()
{
deferred.resolve(false);
});
return deferred.promise;
}];
};
3. You can move get-token-status logic into separate servise
$routeProvider.when("/confirm/:token", {
controller: "confirmEmailController",
templateUrl: "/app/views/confirmEmail.html",
resolve: {
tokenStatus: ['tokenStatusGetterService', function(tokenStatusGetterService){
return tokenStatusGetterService("confirm");
}]
}
})
.when("/reset/:token", {
controller: "resetPasswordController",
templateUrl: "/app/views/resetPasswordEmail.html",
resolve: {
tokenStatus: ['tokenStatusGetterService', function(tokenStatusGetterService){
return tokenStatusGetterService("reset");
}]
}
});
//...
.service('tokenStatusGetterService', ["$q", "$route", "tokenService", function($q, $route, tokenService) {
return function(tokenType) {
var deferred = $q.defer();
tokenService
.getTokenStatus($route.current.params.token, tokenType)
.success(function(response)
{
deferred.resolve(true);
})
.error(function()
{
deferred.resolve(false);
});
return deferred.promise;
};
}]);
one way to do it is to put a function on your getTokenStatus service.
this is a simplified example, yet it shows how to pass an argument to your resolve function.
app.factory('getTokenStatus',['$q', '$timeout', '$route', function($q, $timeout, $route){
this.action = function(tokenType) {
var defer = $q.defer();
$timeout(function(){
var res = {
path: $route.current.params.token,
tokenType: tokenType
}
defer.resolve(res);
},1000);
return defer.promise;
}
return this;
}]);
and call it from your resolve object:
app.config(function($routeProvider){
$routeProvider
.when("/123/:token", {
template: "<h1>hello</h1>",
controller: 'testCtrl',
resolve: {
tokenStatus: function(getTokenStatus) {
return getTokenStatus.action('firstToken').then(function(res){
console.log(res);
});
}
}
})
here's a plnkr
I have tried everything to get ui-router's resolve to pass it's value to the given controller–AppCtrl. I am using dependency injection with $inject, and that seems to cause the issues. What am I missing?
Routing
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: 'AppCtrl',
controllerAs: 'vm',
resolve: {
auser: ['User', function(User) {
return User.getUser().then(function(user) {
return user;
});
}],
}
});
Controller
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope'];
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
console.log(auser); // undefined
...
}
Edit
Here's a plunk http://plnkr.co/edit/PoCiEnh64hR4XM24aH33?p=preview
When you use route resolve argument as dependency injection in the controller bound to the route, you cannot use that controller with ng-controller directive because the service provider with the name aname does not exist. It is a dynamic dependency that is injected by the router when it instantiates the controller to be bound in its respective partial view.
Also remember to return $timeout in your example, because it returns a promise otherwise your argument will get resolved with no value, same is the case if you are using $http or another service that returns a promise.
i.e
resolve: {
auser: ['$timeout', function($timeout) {
return $timeout(function() {
return {name:'me'}
}, 1000);
}],
In the controller inject the resolve dependency.
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope','auser']; //Inject auser here
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
vm.user = auser;
}
in the view instead of ng-controller, use ui-view directive:
<div ui-view></div>
Demo
Here is how I work with resolve. It should receive promise. So I create service accordingly.
app.factory('User', function($http){
var user = {};
return {
resolve: function() {
return $http.get('api/user/1').success(function(data){
user = data;
});
},
get: function() {
return user;
}
}
});
This is main idea. You can also do something like this with $q
app.factory('User', function($q, $http){
var user = {};
var defer = $q.defer();
$http.get('api/user/1').success(function(data){
user = data;
defer.resolve();
}).error(function(){
defer.reject();
});
return {
resolve: function() {
return defer.promise;
},
get: function() {
return user;
}
}
});
These are almost identical in action. The difference is that in first case, service will start fetching date when you call resolve() method of service and in second example it will start fetch when factory object is created.
Now in your state.
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: function ($scope, $rootScope, User) {
$scope.user = User.get();
console.log($scope.user);
},
controllerAs: 'vm',
resolve: {
auser: function(User) {
return User.resolve()
}
}
});