ui-router test failed with event.preventDefault() in $stateChangeStart - angularjs

I have problem with my angular test.
describe('Routes test', function() {
//Initialize global variables
var $state,
$location;
// Load the main application module
beforeEach(module("MyApp"));
beforeEach(inject(function(_$location_, _$state_, $templateCache) {
$state = _$state_;
$location = _$location_;
// We need add the template entry into the templateCache if we ever
// specify a templateUrl
$templateCache.put('signin.admin.view.html', 'signin');
}));
it('should be redirect to signin if user is not loggedin', function() {
$rootScope.$apply(function() {
$location.path('/');
});
expect($state.current.name).toEqual('signin');
console.log($state.current);
});
});
So I expect signin state because user is not logged in. But from console $state.current is still:
Object{name: '', url: '^', views: null, abstract: true}
To redirect I used this code:
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
if (toState.name.indexOf('signin') === 0 ) {
if( loggedIn ) {
event.preventDefault();
$state.go('layout.dashboard');
}
} else {
if( !loggedIn ) {
event.preventDefault();
$state.go('signin');
}
}
});
Variable loggedIn is false. So I dont understand, when I make test manually in browser $state.current is as expected. But not in this test.
Thank you for your help.

Rather than doing $rootScope.$apply(...) try doing:
$location.path('/');
$rootScope.$broadcast('$locationChangeSuccess');
UI Router listens to the $locationChangeSuccess event that is fired from angular's $location service. When it catches this event it will find the correct state and begin navigating to that state, hence firing the $stateChangeStart event.
Hope this helps!

Related

Angular UI Router dynamic states on refresh goes to 404

I'm loading states dynamically based on the current user role. But when the page is refreshed it takes to the 404 page. Also in $stateChangeStart event fromState.name is blank.
Is there a way to go to the state before refresh button was clicked? Should I store the state before refresh is pressed and then use it?
.state('404', {
url: '/404',
templateUrl: '404.tmpl.html',
controller: function ($scope, $state, APP) {
$scope.app = APP;
$scope.goHome = function () {
$state.go('default.page');
};
}
})
$urlRouterProvider.otherwise('/404');
....
$rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
//fromState.name = '' on refresh
});
Thanks in advance!
The below seems to work, not sure if this is the best approach. On before unload event save the state, then use it in $stateChangeStart.
.run(function ($rootScope, $window, $state, authService, $http, $timeout, localStorageService) {
$rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
if (authService.isLoggedIn()) {
if (toState.name === "404" && fromState.name === '' && localStorageService.get("LAST_STATE") !== "404") {
authService.loadStates($stateProviderRef).then(function () {
$state.go(localStorageService.get("LAST_STATE"), localStorageService.get("LAST_STATE_PARAMS"));
});
}
}
});
window.onbeforeunload = function () {
localStorageService.set("LAST_STATE", $state.current.name);
localStorageService.set("LAST_STATE_PARAMS", $state.params);
return null;
}
})

How to pass url to $stateChangeStart of ui-router

I use the following to redirect routes to login if a user is not logged in:
angular.module('app').run(['$rootScope', '$location', '$state', 'AuthService',
function($rootScope, $location, $state, AuthService) {
/* redirect to login if not logged in */
$rootScope.$on( '$stateChangeStart',
function(e, toState, toParams, fromState, fromParams) {
if(toState.name === "login"){
return; // already going to login
}
if(!AuthService.user) {
e.preventDefault();
$state.go('login');
}
});
}]);
How can I pass the url from the "prevented" state to the login state so that I can continue navigating to that state once logged in?
Example: User clicks link for myapp.com/#/path/to/data --> redirects to myapp.com/#/login --> user logs in --> user routed to myapp.com/#/path/to/data
I basically need /path/to/data to be sent to my login controller so that I can handle that. I cannot seem to find '/path/to/data' in any of the state or param variables in the $stateChangeStart listener.
The $state service has an href method which can create a URL given the state and its parameters. Since those are provided to the state change listener, you can create a URL and then pass it as a param to the login state.
$rootScope.$on('$stateChangeStart',
function(e, toState, toParams, fromState, fromParams) {
if (toState.name === "login") {
return; // already going to login
}
if (!AuthService.user) {
e.preventDefault();
var redirectUrl = $state.href(toState.name, toParams);
$state.go('login', {
redirect: redirectUrl
});
}
});
You'll need to add a redirect parameter to your login state so you can use that.
You can store the pre-login location in $rootScope, and then fetch it from there once login is successful
if(!AuthService.user) {
e.preventDefault();
$rootScope.pre_login_path = $location.path();
$state.go('login');
}
And then after login, you can set the login path similarly in your login controller/service (have that include dependency injection of $rootScope and $location)
$location.path($rootScope.pre_login_path);

Why does $state.transitionTo or $state.go does not display the new HTML partial?

Here is a piece of code destined to check user rights before each UI-router state change. Everything works fine, except the fact that when rights are OK, the transition to the new state (either with $state.go, as below, or with $state.transitionTo), does not seem to do anything at all (the console message is logged but that's all).
angular.module('mymodule', [ /* dependancies */ ])
.run( function($rootScope, $window, $state, AuthManager)
{
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams)
{
if( ! S.isUserConnected() && toParams.requiresLogIn )
{
event.preventDefault();
AuthManager.openConnectionPane().then( function( data )
{
if( AuthManager.isUserConnected() ) {
console.log("This message is correctly printed!");
$state.go( toState.name );
}
});
}
}
);
});
Do you have any idea why this does not work?
EDIT:
I have noticed that the HMTL partial coresponding to the state we are transitioning to, is correctly fetched via a GET request, but it never shows: the old HTML stays displayed (the one from the previous state), even if the URL is correctly updated...
EDIT: add the router config code
In the main module file:
//angular.module('mymodule', [ /* dependancies */ ])
.config( $urlRouterProvider ) {
$urlRouterProvider.otherwise('/home');
}
And in each submodule:
angular.module( 'mymodule.submodule', [ /* dependancies */ ])
.config(function($stateProvider) {
$stateProvider
.state('events-overview', {
url:'/events',
templateUrl: 'app/events/events-overview.html',
controller: 'EventsOverviewCtrl'
})
.state('events-details', {
url:'/events/{eventId:int}',
templateUrl: 'app/events/events-details.html',
controller: 'EventsDetailsCtrl'
})
.state('events-creation', {
url:'/events/new',
params: {
requiresLogIn: true
}
templateUrl: 'app/events/events-creation.html',
controller: 'EventsCreationCtrl'
})
});
I had a similar issue and this is how I solved it:
if( AuthManager.isUserConnected() ) {
//...
event.preventDefault();
$state.go(toState.name, null, {notify: false}).then(function (state) {
$rootScope.$broadcast('$stateChangeSuccess', state, null);
});
}
As it's mentioned in other answers, $state.go doesn't work propertly in .run. Following the advises of this thread I ended up working with this workaround
$urlRouterProvider.otherwise(function ($injector) {
var $state = $injector.get('$state');
$state.go('/home');
});
Not sure if this could help you, but you could try it. Hope it helps
I think your problem will be fixed by doing this :
angular.module('mymodule', [ /* dependancies */ ])
.run( function($rootScope, $window, $state, AuthManager, $timeout)
{
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams)
{
if( ! S.isUserConnected() && toParams.requiresLogIn )
{
AuthManager.openConnectionPane().then( function( data )
{
if( AuthManager.isUserConnected() ) {
console.log("This message is correctly printed!");
$timeout(function() {
event.preventDefault();
$state.go(toState.name);
});
}
});
}
}
);
});
$state.go does not work correctly in .run this should be a workaround by adding the timeout
try putting the event.preventDefault(); bellow the $state.go.
This is where I saw the idea for this suggestion:
angularjs app.run doesn't get called with state
So basically:
AuthManager.openConnectionPane().then( function( data ) {
if( AuthManager.isUserConnected() ) {
console.log("This message is correctly printed!");
$state.go( toState.name );
}
});
event.preventDefault();

Testing event chains and module.run() in Angular + Jasmine

I am trying to test a module that has the following code:
angular.module('angularEnterpriseAuthorization').run(['$rootScope', '$state', 'AppConfig',
function($rootScope, $state, AppConfig) {
// On every time the user changes state check to see if the user has permissions to go to the new state
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
// If the state is not one of the public states as defined in the modules config
if (AppConfig.publicStates.indexOf(toState.name) < 0) {
event.preventDefault();
$state.go(toState, toParams, {notify: false}).then(function() {
$rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
});
}
});
]);
My test looks like this:
beforeEach(module('angularEnterpriseAuthorization', 'coreConfiguration'));
beforeEach(inject(function(_$rootScope_, _$httpBackend_, _AppConfig_) {
$scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
AppConfig = _AppConfig_
spyOn($scope, '$broadcast').andCallThrough();
}));
it('should allow navigation to public states', function() {
$scope.$broadcast('$stateChangeStart', [{},{name:AppConfig.publicStates[0]}]);
expect($scope.$broadcast).toHaveBeenCalledWith('$stateChangeStart', [{}, {name: AppConfig.publicStates[0]}]);
$scope.$broadcast.reset();
expect($scope.$broadcast).toHaveBeenCalledWith('$stateChangeSuccess');
});
The problem I am having is that the second expect is returning false. I think the issues is that the module is not being initialized with the same $rootScope.
Any help would be appreciated!
Thanks
In your run block, you subscribe a $stateChangeStart on $rootScope and also broadcast a $stateChangeSuccess event from $rootScope.
In your test, you have to do the same, using the $rootscope. May be change this line:
$scope = _$rootScope_.$new();
to just this:
$scope = _$rootScope_;
And also you have to remove the $scope.$broadcast.reset(), that will clear all the remembered calls.
To test the second call of the same method, you could do it like this:
it('should allow navigation to public states', function() {
$scope.$broadcast('$stateChangeStart', [{},{name:AppConfig.publicStates[0]}]);
expect($scope.$broadcast).toHaveBeenCalledWith('$stateChangeStart', [{}, {name: AppConfig.publicStates[0]}]);
$scope.$apply();
expect($scope.$broadcast.calls[1].args[0]).toEqual('$stateChangeSuccess');
});
Hope this helps.

Conditional Page Display when application opens for the first time

Hi I am just starting to learn angular && angular-ui-router and am trying to figure out how to determine when the app is opened for the first time to send the user to the login page or home page.
This is what i have so far:
codeArtApp.config(function($stateProvider, $urlRouterProvider){
$stateProvider
.state('login',{
url : '/login',
templateUrl:'App/scripts/login/loginView.html',
controller: 'loginCtrl'
})
.state('profile', {
url : '/profile',
templateUrl:'App/scripts/login/loginView.html',
controller: 'profileCtrl'
})
.state('404', {
url: '/404',
templateUrl:'App/scripts/views/404.html'
});
$urlRouterProvider.otherwise("404");
});
codeArtApp.run(['$state', '$rootScope', function($state, $rootScope, $log){
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState){
if(fromState.url === '^' && toState.url !== 'login'){
$state.go('login');
}
});
}]);
What I assumed here is that if fromState.url is set to ^ then the application is starting for the first time.
For some reason this code enters an infinite loop and I gt this stacktrace:
Now from what I can tell this happened because event if $state.go('login') gets execute toState.url is always 404.
I had hoped if $state.go('login') gets executed toState.url would have been set to login and that call would not be executed anymore.
I may not be setting this logic in the right place...
Can anyone tell me how conditionaly page displayed is achieved in Angular?
In your $urlRouterProvider.otherwise call you can pass a function instead of a string. Something like this:
$urlRouterProvider.otherwise(function($injector){
var $state = $injector.get('$state');
var Storage = $injector.get('Storage');
if (Storage.has('authToken')) {
$state.go('home');
}
else {
$state.go('login');
}
});
Hope this helps!
There is a link to working plunker, the main changes are shown in the code:
codeArtApp.run(['$state', '$rootScope',
function($state, $rootScope, $log) {
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState) {
// instead of
// toState.url !== 'login'
// we compare
// toState.name !== 'login'
var shouldLogin = fromState.url === '^'
&& toState.name !== 'login';
if(shouldLogin)
{
// this way we stop current execution
event.preventDefault();
// and navigate to login
$state.go('login');
}
});
}
]);
The toState.url would contain the url, i.e. '/login' instead of 'login'. Also check the : State Change Events to see more about the event.preventDefault(); . The plunker showing the above...

Resources