I need to switch view in my angular js webapp.
In order to handle the routing, I am using the $stateProvider, as here:
.config(['$httpProvider', '$stateProvider', '$urlRouterProvider', function ($httpProvider, $stateProvider, $urlRouterProvider) {
$stateProvider
.state('common', {
templateUrl: 'assets/html/template.html',
abstract: true,
})
.state('login', {
url: '/login',
parent: 'common',
templateUrl: 'app/models/authentication/views/login.html',
controller: 'LoginController'
})
.state('home', {
url: '/',
parent: 'common',
templateUrl: 'app/models/home/views/home.html',
controller: 'HomeController'
})
...
Inside my login controller, I am trying to switch the view (from Login to Home)
.controller('LoginController', ['$scope', '$rootScope', '$cookieStore', '$location', '$window', '$http',
function ($scope, $rootScope, $cookieStore, $location, $window, $http) {
$scope.login = function () {
loadHttp();
};
function loadHttp() {
var url = "http://myurl";
...
.then(function (response) {
$scope.$apply(function () {
$location.path('/home');
console.log("$location.path: " + $location.path);
});
});
}
But when I reach $location.path('/home'); I get this error:
Error: [$rootScope:inprog] http://errors.angularjs.org/1.4.8/$rootScope/inprog?p0=%24digest
at Error (native)
at http://localhost:3000/adminTool/assets/js/angular.min.js:6:416
at t (http://localhost:3000/adminTool/assets/js/angular.min.js:126:132)
at r.$apply (http://localhost:3000/adminTool/assets/js/angular.min.js:133:515)
at http://localhost:3000/adminTool/app/models/authentication/controllers.js:46:32
at http://localhost:3000/adminTool/assets/js/angular.min.js:119:129
at r.$eval (http://localhost:3000/adminTool/assets/js/angular.min.js:133:313)
at r.$digest (http://localhost:3000/adminTool/assets/js/angular.min.js:130:412)
at r.$apply (http://localhost:3000/adminTool/assets/js/angular.min.js:134:78)
at g (http://localhost:3000/adminTool/assets/js/angular.min.js:87:444)
What am I doing wrong? How to get rid of it and finally switch view?
I read to use $apply from SO
PS: I am very new to angular
The error you get is because of the $scope.$apply around the $location.path('/home') statement. Angular is already in a digest loop so triggering it again within that loop (by calling $apply) will give you this error. Removing the $scope.$apply will therefor probably fix your problem.
The reason the digest loop is already triggered is because you are probably using the $http service which uses a promise to return the value. Angular always triggers a digest when resolving this promise.
But aside from that you probaly want to use the $state service to move to another state instead of moving to another location using the $location service. $state.go('home') would probably be what you are looking for.
I think here is the problem $location.path('/home');
You don't have path /home you have state called home
so you need to go $location.path('/'); or inject $state and use method $state.go('home'), also you do not need to wrap it inside $apply
Related
I have userAccess flag in controller if it returns false i want hide all the application from user and redirect user to access.html with some access required form So with below code it throws error transition superseded, Any idea how to achieve this task with angularjs ui.router ?
mainCtrl.js
$scope.cookie = $cookies.get(jklHr');
var parts = $scope.cookie.split("|");
var uidParts = parts[7].split(",");
$scope.newUser._id = uidParts[0];
var userAccess = AuthService.getCurrentUser($scope.newUser._id);
if(!userAccess) {
console.log("Access Deinied");
$state.go('app.access');
}
app.js
angular.module('App', [
'ui.router',
'ui.bootstrap',
'ui.bootstrap.pagination',
'ngSanitize',
'timer',
'toastr',
'ngCookies',
]).config(function($stateProvider, $httpProvider, $urlRouterProvider) {
'use strict'
$urlRouterProvider.otherwise(function($injector) {
var $state = $injector.get('$state');
$state.go('app.home');
});
$stateProvider
.state('app', {
abstract: true,
url: '',
templateUrl: 'web/global/main.html',
controller: 'MainCtrl'
})
.state('app.home', {
url: '/',
templateUrl: 'view/home.html',
controller: 'MainCtrl'
})
.state('app.dit', {
url: '/dit',
templateUrl: 'view/partials/logs.html',
controller: 'LogsCtrl',
resolve: {
changeStateData: function(LogsFactory) {
var env = 'dit';
return LogsFactory.resolveData(env)
.then(function(response) {
return response.data
});
}
}
})
.state('app.access', {
url: '/access',
templateUrl: 'view/partials/access.html',
controller: 'AccessCtrl'
});
});
Create an interceptor, all http class will go thrown the interceptor. Once the "resolve" piece is executed and return 401 you can redirect to the login screen or 403 to the forbidden view.
https://docs.angularjs.org/api/ng/service/$http
The problem is that you are trying to change a state while a previous state change is still in course.
The ui-router has events for when a state change starts and ends.
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
});
So your redirect should be in there. Anyway I recommend you move that user check to a higher level in your app, like .run(), with some exception for the login states. That way you won't have to check in every controller individually.
Make sure you've most updated version of angularjs & angular-ui. If you're using older version then check compatibility of angular-ui version with your angular version. https://github.com/angular-ui/ui-router/issues/3246
If that doesn't work, add following line inside app.config
$qProvider.errorOnUnhandledRejections(false)
don't forget add dependency $qProvider in config function.
I am currently using ui router as below: -
$stateProvider
.state('login', {
url: '/login'
, resolve: loadSequence(
'base')
, templateUrl: 'app/shared/main/login-main.html'
, controller: 'mainController'
, abstract: true
})
.state('login.signin', {
url: '/signin'
, resolve: loadSequence(
'login-items'
, 'spin'
, 'ladda'
, 'angular-ladda'
, '_loginController'
)
, templateUrl: "app/components/login/login_login.html"
, controller: 'loginController'
});
Now in loginController I want to be able to access a function in the mainController.
Is that possible with my current implementation:-
angular.module('app').controller('mainController', function($scope, $state) {
$scope.showWarning= function(){
//show warning
}
});
angular.module('app').controller('loginController', function($scope, $state) {
// I want to access $scope.showWarninghere;
});
Extract the getData() method out into a service and then you can inject it into both controllers:
angular.module('app').factory('dataService', function () {
return {
getData: function() { ... }
}
});
angular.module('app').controller('mainController', function($scope, $state, dataService) {
// You probably don't need to put this into your scope, but if you do:
$scope.getData = dataService.getData.bind(dataService);
});
angular.module('app').controller('loginController', function($scope, $state, dataService) {
dataService.getData();
});
It is useful to remember that scopes and controllers are created and destroyed as you navigate between states, so anything that actually wants to exist in more than one state really does want to be stored in a service.
As said by Duncan and Sanjay, it might indeed be a better idea to use a service to get the data, but I thought I'd answer the original question so you know :
As stated in the docs, for prototypal inheritance to be active the views must be nested, not only the states.
So in order for loginController to have access to the scope of mainController, the login-main.html template must use the uiView directive (e.g <div ui-view></div>) which will be the placeholder for the login_login.html template of the login.signin state.
you can use angular service for this.
I found a Plunker code to resolve your problem
I am trying to redirect a user to different page after user is authenticated. I am using jwt authentication and I tried with $location, $window for redirection but its throwing error $state.go is not a function. I am new to angular and I am guessing there should be way to redirect may using a service in angular but I am still new to factories and service.
I have my state provider like this in state.js file:
myApp.config(function ($stateProvider, $urlRouterProvider) {
// default route
$urlRouterProvider.otherwise("/Home");
var header = {
templateUrl: 'commonViews/Header.html',
controller: function ($scope) {
}
};
var footer = {
templateUrl: 'commonViews/Footer.html',
controller: function ($scope) {
}
};
// ui router states
$stateProvider
.state('Home', {
url: "/Home",
views: {
header: header,
content: {
templateUrl: 'views/HomePage.html',
controller: function ($scope) {
}
},
footer: footer
}
})
.state('LoggedIn', {
url: "/LoggedIn",
views: {
'header': header,
'content': {
templateUrl: 'views/LoggedIn.html',
controller: function () {
}
},
'footer': footer
}
});
});
and loginController.js:
myApp.controller('loginController', ['$scope', '$http', 'jwtHelper', '$localStorage', '$state', function ($scope, $http, jwtHelper, $localStorage, $sessionStorage, $state)
{
$scope.email = "";
$scope.password = "";
$scope.token = "";
$scope.loginForm = function () {
var data = {email: $scope.email, password: $scope.password};
var url = 'rs/loginResource/login';
$http.post(url, data).then(function (response)
{
$localStorage.token = response.data.token;
console.log("Encoded Token in localstorage is:" + $localStorage.token);
if ($localStorage.token) {
// $location.url("/LoggedIn");
$state.go('/LoggedIn');
}
}, function (error)
{
console.log("error", error);
});
};
}]);
further I have to perform refresh token based on expiration time etc, so is it better to have separate the functions like using a service to do the signup and signin?
The problem is the definition of your controller and the way you're handling your injections. And no, referring to your own answer to your question, the problem is not the "order" of the injections. It's a bit worse.
myApp.controller('loginController', ['$scope', '$http', 'jwtHelper', '$localStorage', '$state', function ($scope, $http, jwtHelper, $localStorage, $sessionStorage, $state)
in this code you're mapping '$scope' to a $scope variable, '$http' to $http, 'jwtHelper' to jwtHelper, '$localStorage' to $localStorage and '$state' to $sessionStorage, and you're not mapping anything to $state. So obviously you get an error when you try to call a method on an undefined $state variable.
So in short, you're injecting 5 dependencies and you're assigning 6 variables to your dependencies, which in turn results in things not doing what they're supposed to do.
You can use Angular $window:
$window.location.href = '/index.html';
$state. go accepts the view name, not the URL. Replace '/LoggedIn' with Logged and you should be good.
Use $state.go instead of $window and location.
Add $state injector on controller
write code $state.go('LoggedIn');
Instead $state.go('/LoggedIn');
Write state name('LoggedIn') instead of url('/LoggedIn').
Hopefully this will work in your case.
You need to add $window as a dependency to your controller if you are using $window,
myApp.controller('loginController', ['$scope', '$http', 'jwtHelper', '$localStorage', '$state','$window', function ($scope, $http, jwtHelper, $localStorage, $sessionStorage, $state,$window)
{
$window.location.href = '/index.html';
}
otherwise change the route like this, here also you need to inject $state,
myApp.controller('loginController', ['$scope', '$http', 'jwtHelper', '$localStorage', '$state','$window','$state', function ($scope, $http, jwtHelper, $localStorage, $sessionStorage, $state,$window,$state)
{
$state.go('LoggedIn');
}
Is there a way to inject automatically a dependency (or a resolve) in the $scope without manually append it to $scope ?
(with or without UI-Router)
Would the "controllerAs" syntax be of any help ?
.config(['$stateProvider',
function($stateProvider) {
$stateProvider
.state('test', {
url: '/test',
templateUrl: 'test.tpl.html',
controller: 'TestCtrl',
})
;
}
])
.controller('TestCtrl', ['$scope', 'myService',
function($scope, $rootScope, accruals, myService) {
$scope.myService= myService; // how can I avoid this every time ?
}
])
Thanks
I am trying to do what was essentially answered here Unable to open bootstrap modal window as a route
Yet my solution just will not work. I get an error
Error: [$injector:unpr] Unknown provider: $modalProvider <- $modal
My app has the ui.bootstrap module injected - here is my application config
var app = angular.module('app', ['ui.router', 'ui.bootstrap','ui.bootstrap.tpls', 'app.filters', 'app.services', 'app.directives', 'app.controllers'])
// Gets executed during the provider registrations and configuration phase. Only providers and constants can be
// injected here. This is to prevent accidental instantiation of services before they have been fully configured.
.config(['$stateProvider', '$locationProvider', function ($stateProvider, $locationProvider) {
// UI States, URL Routing & Mapping. For more info see: https://github.com/angular-ui/ui-router
// ------------------------------------------------------------------------------------------------------------
$stateProvider
.state('home', {
url: '/',
templateUrl: '/views/index',
controller: 'HomeCtrl'
})
.state('transactions', {
url: '/transactions',
templateUrl: '/views/transactions',
controller: 'TransactionsCtrl'
})
.state('login', {
url: "/login",
templateUrl: '/views/login',
controller: 'LoginCtrl'
})
.state('otherwise', {
url: '*path',
templateUrl: '/views/404',
controller: 'Error404Ctrl'
});
$locationProvider.html5Mode(true);
}])
I have reduced my controller to the following:
appControllers.controller('LoginCtrl', ['$scope', '$modal', function($scope, $modal) {
$modal.open({templateUrl:'modal.html'});
}]);
Ultimately, what I am hoping to achieve is when login is required not actually GO to the login page, but bring up a dialog.
I have also tried using the onEnter function in the ui-router state method. Couldn't get this working either.
Any ideas?
UPDATE
Ok - so as it turns out, having both ui-bootstrap.js AND ui-bootstrap-tpls breaks this - After reading the docs I thought you needed the templates to work WITH the ui-bootstrap. though it seems all the plunkers only load in the ..tpls file - once I removed the ui-bootstrap file my modal works...Am i blind? or doesn't it not really say which one you need in the docs on github? -
Now i just need to figure out how to prevent my url from actually going to /login, rather than just show the modal :)
update 2
Ok, so by calling $state.go('login') in a service does this for me.
Hi I had a hard time getting through the similar problem.
However, I was able to resolve it.
This is what you would probably need.
app.config(function($stateProvider) {
$stateProvider.state("managerState", {
url: "/ManagerRecord",
controller: "myController",
templateUrl: 'index.html'
})
.state("employeeState", {
url: "empRecords",
parent: "managerState",
params: {
empId: 0
},
onEnter: [
"$modal",
function($modal) {
$modal.open({
controller: "EmpDetailsController",
controllerAs: "empDetails",
templateUrl: 'empDetails.html',
size: 'sm'
}).result.finally(function() {
$stateProvider.go('^');
});
}
]
});
});
Click here for plunker. Hope it helps.
I'm working on something similar and this is my solution.
HTML code
<a ui-sref="home.modal({path: 'login'})" class="btn btn-default" ng-click="openModal()">Login</a>
State configuration
$stateProvider
// assuming we want to open the modal on home page
.state('home', {
url: '/',
templateUrl: '/views/index',
controller: 'HomeCtrl'
})
// create a nested state
.state('home.modal', {
url: ':path/'
});
Home controller
//... other code
$scope.openModal = function(){
$modal.open({
templateUrl: 'path/to/page.html',
resolve: {
newPath: function(){
return 'home'
},
oldPath: function(){
return 'home.modal'
}
},
controller: 'ModalInstanceController'
});
};
//... other code
Finally, the modal instance controller.
This controller synchronizes the modal events (open/close) with URL path changes.
angular.module("app").controller('ModalInstanceController', function($scope, $modalInstance, $state, newPath, oldPath) {
$modalInstance.opened.then(function(){
$state.go(newPath);
});
$modalInstance.result.then(null,function(){
$state.go(oldPath);
});
$scope.$on('$stateChangeSuccess', function () {
if($state.current.name != newPath){
$modalInstance.dismiss('cancel')
}
});
});
You may create a state with the same templateUrl and controller as your page where you want to show the modal, adding params object to it
$stateProvider
.state('root.start-page', {
url: '/',
templateUrl: 'App/src/pages/start-page/start-page.html',
controller: 'StartPageCtrl'
})
.state('root.login', {
url: '/login',
templateUrl: 'App/src/pages/start-page/start-page.html',
controller: 'StartPageCtrl',
params: {
openLoginModal: true
}
})
And in controller of the page, use this parameter to open the modal
.controller("StartPageCtrl", function($scope, $stateParams) {
if ($stateParams.openLoginModal) {
$scope.openLoginModal();
}
I found a handy hint to get this working. There are probably caveats, but it works for me. You can pass a result still but I have no need for one.
Using finally instead of the then promise resolve sorted this for me. I also had to store the previous state on rootScope so we knew what to go back to.
Save previous state to $rootScope
$rootScope.previousState = 'home';
$rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from, fromParams){
$rootScope.previousState = from.name;
})
State using onEnter
$stateProvider.state('contact', {
url: '/contact',
onEnter: function ($state, $modal, $rootScope){
$modal.open({
templateUrl: 'views/contact.html',
controller: 'ContactCtrl'
}).result.finally(function(){
$state.go($rootScope.previousState);
})
}
});