How to refresh resolve? - javascript

Using ui-router, I have a state with a resolve function:
.state('tab.social', {
url: '/social/',
views: {
'menuContent': {
templateUrl: 'templates/social/tab-social.html',
controller: 'SocialCtrl',
resolve: {
socialAuthResolve: socialAuthResolve
}
}
}
})
I capture the resolve in the controller as follows:
.controller('SocialCtrl', function($scope, SocialAuth, socialAuthResolve) {
//
console.log(socialAuthResolve);
//
$scope.logOut = function() {
SocialAuth.logOut();
$state.go('tab.social', {}, {reload: true});
};
//
$scope.logIn= function() {
SocialAuth.logIn();
$state.go('tab.social', {}, {reload: true});
};
})
However, when either pressing logOut or logIn, my state is not refreshed. I am using some parameters from the resolve socialAuthResolve and would like this to be updated. In this case, the old parameters are still in there.
Only when I refresh my browser, then the variables and the page are updated accordingly. How can I refresh the page after the logOut and logIn? For instance, force to resolve again?

Here is a sample state with config:
.state('app.stateName', {
url: "/theUrl",
views: {
'myViewName': {
templateUrl: "templates/template.html",
controller: 'SomeController',
resolve: {
pleaseResolve: function() {
console.log("I am resolved");
}
}
}
}
})
In my controller (assuming SomeController as mentioned above), whenever I enter into the state I run this.
var res = ($state.$current.self.views.myViewName.resolve.pleaseReslove)
res.call()
This will call my resolve function every time I come into the view.

Related

watch parent controller value from ui router state resolve

In my AngularJS app using ui-router I have three states. The parent state in the controller resolves a promise and on a successful request, executes the code.
In the child state portfolio.modal.patent, using $stateProvider and the resolve method, I make another request. The issue is that I need to make the resolve method wait until the promise is returned in the parent controller portfolio.
States
$stateProvider
.state('portfolio', {
url: '/portfolio',
templateUrl: 'app/templates/portfolio/portfolio.tpl.htm',
controller: 'portfolioCtrl',
controllerAs: '$ctrl',
})
.state('portfolio.modal', {
abstract: true,
views: {
"modal": {
templateUrl: "app/templates/patent/modal.html"
}
}
})
.state('portfolio.modal.patent', {
url: '/:patentId',
resolve: { //NEED TO MAKE THIS RESOLVE AFTER PORTFOLIO CONTROLLER HAS RETURNED ITS PROMISE
patent: ['$stateParams', 'patentsRestService', function($stateParams, patentsRestService) {
return patentsRestService.fetchPatentItem($stateParams.patentId);
}],
}
}
Portfolio controller
function portfolioCtrl() {
$scope.promise
.then(function(response) {
//WHEN PROMISIS RETURNED, THEN RESOLVE DATA IN PORTFOLIO.MODAL.PATENT
})
}
Question
How do I resolve the child state portfolio.modal.patent data after the promise has been returned from the parent state controller portfolioCtrl
Here is a fast possible workaround how you can try to solve your problem using service as a bridge between requests
Example:
// service
const bridge = $q.defer();
const resolveBridgePromise = data => bridge.resolve(data);
const getBridgePromise = () => bridge.promise;
const fetchDataInParentCtrl = () => {
$timeout(() => {
console.log('data fetched in parent');
resolveBridgePromise({ key: 'fetched data' });
}, 2000);
};
// controller
fetchDataInParentCtrl();
// resolver
getBridgePromise()
.then(data => {
console.log('Do http request here for your child data: ', data);
});

Resolve promise in abstract parent before $stateChangeStart fires

I'm attempting to resolve some server-side data in an abstract parent state before the Home child state is loaded. I want to make sure I have the user's full name for examination in the state's data.rule function. However, using console.log statements, I can see the userFullName prints as an empty string to the console before the "done" statement in the factory.privilegeData method.
EDIT
The data in the parent resolve is security data from the server that needs to be available globally before any controllers are initialized. Only one child state is listed here (for the sake of readability), but all states, outside of login are children of the abstract parent. Once the parent's resolves are complete the data is stored in the $rootScope and used to determine access rights via the data.rule function in each state.
config.js
UPDATE: per answer below I've updated the parent/child states as such, howeve I'm still running into the same issue:
.state('app', {
url:'',
abstract: true,
template: '<div ui-view class="slide-animation"></div>',
resolve:{
privileges: ['$q', 'privilegesService', function($q, privilegesService){
console.log('from parent resolve');
return privilegesService.getPrivileges()
.then(privilegesService.privilegesData)
.catch(privilegesService.getPrivilegesError);
}]
}
})
.state('app.home', {
url: '/home',
templateUrl: 'app/components/home/home.html',
controller: 'HomeController',
controllerAs: 'vm',
parent: 'app',
resolvePolicy: {when:'LAZY', async: 'WAIT'},
authenticate: true,
data:{
rule: function($rootScope){
console.log("from home state data rule");
return true;
}
}
})
privilegesService.js
factory.getPrivileges = function () {
console.log('from server call');
var queryString = "&cmd=privilege&action=user";
return $http.get(config.serviceBaseUri + queryString);
};
factory.privilegesData = function(priv){
console.log('from privilege data');
if(priv && priv.data) {
$rootScope.userFullName = priv.data.firstName + ' ' + priv.data.lastName;
}
console.log('done');
};
Based on the console statments above I'm getting the following output. I need the from home state data rule to occur last...
...since I'm using the the results of the data.rule function for authorization for each state. Below is the $stateChangeStart from my .run method
app.route.js
$rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams) {
if(toState.authenticate){
if(authService.authKeyExists()){
if(toState.data.rule($rootScope)){
navigationService.addNavObject("activity", {
summary : "Page navigation",
page : $location.absUrl().replace("#/", "")
});
} else {
$state.go('app.home');
}
} else {
event.preventDefault();
$state.go('login');
}
}
});
The child states need the parent state as a dependency in order for it to wait to load, even if its not used directly. Source
you have to try something like the following instead :
.state('app.home', {
url: '/home',
templateUrl: 'app/components/home/home.html',
controller: 'HomeController',
controllerAs: 'vm',
parent: 'app',
authenticate: true,
data:{
rule: function($rootScope){
console.log($rootScope.userFullName);
return true;
}
}
})
You can use the property resolvepolicy and set it to LAZY as well, a state's LAZY resolves will wait for the parent state's resolves to finish before beginning to load.

In Angular ui-router how to emit an event from one view to another view?

The use case is to change login button to text "logged in as xxx" after authentication.
I have devided my page to 3 views: header, content, footer. The login button is in the header view. When I click login, it transits to "app.login" state, and the content view changes to allow user input username and password.
Here's the routing code:
app.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: '/',
views: {
'header': {
templateUrl: 'static/templates/header.html',
controller: 'AppController'
},
'content': {
templateUrl: 'static/templates/home.html',
controller: 'HomeController'
},
'footer': {
templateUrl: 'static/templates/footer.html',
}
}
})
.state('app.login', {
url: 'login',
views: {
'content#': {
templateUrl : 'static/templates/login.html',
controller : 'LoginController'
}
}
})
The html template has code like this:
<li><span ng-if='loggedIn' class="navbar-text">
Signed in as {{currentUser.username}}</span>
</li>
LoginController set a $scope.loggedIn flag to true once authentication succeeded, but how can I populate that flag to the header view?
As I understand it I can't just use $scope.loggedIn in the html template as above because the $scope is different in two controllers. I know if LoginController is a child of AppController, then I can call $scope.$emit in LoginController with an event and call $scope.$on in AppController to capture it. But in this case the two controllers are for different views, how can I make them parent-child?
I know I can use $rootScope but as I'm told polluting $rootScope is the last resort so I'm trying to find a best practise. This must be a very common use cases so I must be missing something obvious.
You can use a factory to handle authentication:
app.factory( 'AuthService', function() {
var currentUser;
return {
login: function() {
// logic
},
logout: function() {
// logic
},
isLoggedIn: function() {
// logic
},
currentUser: function() {
return currentUser;
}
};
});
Than can inject the AuthService in your controllers.
The following code watches for changes in a value from the service (by calling the function specified) and then syncs the changed values:
app.controller( 'AppController', function( $scope, AuthService ) {
$scope.$watch( AuthService.isLoggedIn, function ( isLoggedIn ) {
$scope.isLoggedIn = isLoggedIn;
$scope.currentUser = AuthService.currentUser();
});
});
In such cases I typically opt to use a service to coordinate things. Service's are instantiated using new and then cached, so you effectively get a singleton. You can then put in a simple sub/pub pattern and you're good to go. A basic skeleton is as follows
angular.module('some-module').service('myCoordinationService', function() {
var callbacks = [];
this.register = function(cb) {
callbacks.push(cb);
};
this.send(message) {
callbacks.forEach(function(cb) {
cb(message);
});
};
}).controller('controller1', ['myCoordinationService', function(myCoordinationService) {
myCoordinationService.register(function(message) {
console.log('I was called with ' + message);
});
}).controller('controller2', ['myCoordinationService', function(myCoordinationService) {
myCoordinationService.send(123);
});
Do you use any serivce to keep logged user data? Basically serivces are singletons so they are good for solving that kind of problem without polluting $rootScope.
app.controller('LoginController', ['authService', '$scope', function (authService, $scope) {
$scope.login = function(username, password) {
//Some validation
authService.login(username, password);
}
}]);
app.controller('HeaderController', ['authService', '$scope', function (authService, $scope) {
$scope.authService = authService;
}]);
In your header html file:
<span ng-if="authService.isAuthenticated()">
{{ authService.getCurrentUser().userName }}
</span>

AngularJS ui router $stateChangeStart with promise inifinite loop

I'm trying to build some sort of authentication in my angular app and would like to redirect to a external URL when a user is not logged in (based on a $http.get).
Somehow I end up in an infinite loop when the event.preventDefault() is the first line in the $stateChangeStart.
I've seen multiple issues with answers on stackoverflow, saying like "place the event.preventDefault() just before the state.go in the else". But then the controllers are fired and the page is already shown before the promise is returned.
Even when I put the event.preventDefault() in the else, something odd happens:
Going to the root URL, it automatically adds the /#/ after the URL and $stateChangeStart is fired multiple times.
app.js run part:
.run(['$rootScope', '$window', '$state', 'authentication', function ($rootScope, $window, $state, authentication) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
event.preventDefault();
authentication.identity()
.then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
return;
} else {
$state.go(toState, toParams);
}
});
});
}]);
authentication.factory.js identity() function:
function getIdentity() {
if (_identity) {
_authenticated = true;
deferred.resolve(_identity);
return deferred.promise;
}
return $http.get('URL')
.then(function (identity) {
_authenticated = true;
_identity = identity;
return _identity;
}, function () {
_authenticated = false;
});
}
EDIT: Added the states:
$stateProvider
.state('site', {
url: '',
abstract: true,
views: {
'feeds': {
templateUrl: 'partials/feeds.html',
controller: 'userFeedsController as userFeedsCtrl'
}
},
resolve: ['$window', 'authentication', function ($window, authentication) {
authentication.identity()
.then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
}
})
}]
})
.state('site.start', {
url: '/',
views: {
'container#': {
templateUrl: 'partials/start.html'
}
}
})
.state('site.itemList', {
url: '/feed/{feedId}',
views: {
'container#': {
templateUrl: 'partials/item-list.html',
controller: 'itemListController as itemListCtrl'
}
}
})
.state('site.itemDetails', {
url: '/items/{itemId}',
views: {
'container#': {
templateUrl: 'partials/item-details.html',
controller: 'itemsController as itemsCtrl'
}
}
})
}])
If you need more info, or more pieces of code from the app.js let me know !
$stateChangeStart will not wait for your promise to be resolved before exiting. The only way to make the state wait for a promise is to use resolve within the state's options.
.config(function($stateProvider) {
$stateProvider.state('home', {
url: '/',
resolve: {
auth: function($window, authentication) {
return authentication.identity().then(function (identity) {
if (!authentication.isAuthenticated()) {
$window.location.href = 'external URL';
}
});
}
}
});
});
By returning a promise from the function, ui-router won't initialize the state until that promise is resolved.
If you have other or children states that need to wait for this, you'll need to inject auth in.
From the wiki:
The resolve keys MUST be injected into the child states if you want to wait for the promises to be resolved before instantiating the children.

Conditionally navigate to state with angular ui-router

Currently, we have a 'Portfolio' tool in beta. Once a user logs in to the main app, if they have been given access to the beta, they can navigate to the Portfolio tool directly, without any additional login. If not, they should be redirected to a Portfolio login page (state is called portfolio.login) where they can login or contact support/sales etc. Right now I have the check in the resolve block, however $state.go('portfolio.login') seems to fetch the right partials, but doesn't render them on screen or navigate to the appropriate URL.
Code:
angular.module('portfolio.manager').config(function ($logProvider, $stateProvider) {
'use strict';
$stateProvider
.state('portfolio.manager', {
url: '/manager',
resolve: {
CheckLoggedIn: function ($state, loggedIn) {
var _loggedIn = loggedIn.checkUser();
if (!_loggedIn) {
$state.go('portfolio.login');
console.log('not authorized');
}
},
portfolioAuthService: 'portfolioAuthService',
User: function(portfolioAuthService){
return portfolioAuthService.getUser();
},
Portfolios: function (User, portfolioManagerService) {
return portfolioManagerService.getPortfolios();
}
},
views: {
'main#': {
templateUrl: 'app/portfolio/manager/portfolio-manager.html',
controller: 'PortfolioManagerCtrl'
},
'no-portfolios#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/no-portfolios.html'
},
'create#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/create.html'
}
}
})
I ran in the same problem days ago. Instead of using resolve, I check if the user is logged when state changes, defining run module and listening $stateChangeStart event, then check if the current state required authentication. If so, check if the user is logged in.
angular.module('portfolio.manager').config(function ($logProvider, $stateProvider) {
'use strict';
$stateProvider
.state('portfolio.manager', {
url: '/manager',
resolve: {
portfolioAuthService: 'portfolioAuthService',
User: function(portfolioAuthService){
return portfolioAuthService.getUser();
},
Portfolios: function (User, portfolioManagerService) {
return portfolioManagerService.getPortfolios();
}
},
data: {
requiredAuthentication: true
},
views: {
'main#': {
templateUrl: 'app/portfolio/manager/portfolio-manager.html',
controller: 'PortfolioManagerCtrl'
},
'no-portfolios#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/no-portfolios.html'
},
'create#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/create.html'
}
}
})
})
.run(run);
run.$inject = ['$rootScope','$state','loggedIn'];
function run($rootScope,$state,loggedIn){
$rootScope.$on('$stateChangeStart',function(e,toState){
if ( !(toState.data) ) return;
if ( !(toState.data.requiredAuthentication) ) return;
var _requiredAuthentication = toState.data.requiredAuthentication;
if (_requiredAuthentication && !loggedIn.checkUser() ){
e.preventDefault();
$state.go('portfolio.login', { notify: false });
console.log('not authorized');
}
return;
});
};

Categories

Resources