I'm having a issue in my Angular app with regards to the resolve section of the ui-router. I'm trying to present a login modal the first time a user hits the website.
In my app.config.js, how do I inject my 'loginService':
angular
.module('rage')
.config(config);
function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/dashboard");
$stateProvider
.state('main', {
url: "/dashboard",
templateUrl: "app/views/dashboard.html",
controller: 'MainCtrl',
data: { pageTitle: 'RAGE', requireLogin: true },
resolve: {
// *** WHERE DO I INJECT 'loginService' ? ***
authUser: function () {
return loginService.loginModal().then(function (user) {
$rootScope.userID = user.userId;
userService.openUserSession(razorEnvJson).then(function (data) {
// assign some scope vars here..
});
})
}
}
})
.state('login', {
url: "/login",
templateUrl: "app/views/login-view.html",
controller: 'LoginCtrl'
})
}
loginService code:
(function () {
'use strict';
angular.module('rage').service('loginService',
['$rootScope', '$modal', 'datacontext', 'userService', login]);
function login($rootScope, $modal, datacontext, userService) {
var modalInstance = null
this.loginModal = function(){
modalInstance = $modal.open({
animation: true,
templateUrl: 'app/components/login/login.html',
controller: 'LoginCtrl as login',
});
return modalInstance.result.then(function (user) {
return user;
});
};
}
})();
LoginCtrl controller code:
(function () {
'use strict';
angular.module('rage').controller('LoginCtrl',
['$rootScope', '$scope', '$modalInstance', '$q', 'datacontext', 'userService', authenticate]);
function authenticate($rootScope, $scope, $modalInstance, $q, datacontext, userService) {
var login = this;
// OK,CANCEL CLICK EVENTS FROM MODAL !!!
$scope.ok = function () {
// var user = userService.authenticateWebUser(); // **** TBD ****
var user = {userId: login.userId, pswd: login.pswd};
$modalInstance.close(user);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
})();
I've also attempted $rootScope.$on('$stateChangeStart' event inside app.js to transition the state from main to login, but that hangs up on me.
**** MY UPDATED APP.CONFIG.JS CODE, SEPT 18 ****
Here is the proper usage of resolve: using ui-router states.
function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/dashboard");
$stateProvider
.state('main', {
url: "/dashboard",
templateUrl: "app/views/dashboard.html",
controller: 'MainCtrl',
data: { pageTitle: 'RAGE', requireLogin: true },
resolve: {
authUser: ['$rootScope', 'loginService', 'userService', function ($rootScope, loginService, userService) {
return loginService.loginModal().then(function (user) {
$rootScope.userID = user.userId;
initSession(user, $rootScope, loginService, userService);
})
}]
}
})
.state('login', {
url: "/login",
templateUrl: "app/views/login-view.html",
controller: 'LoginCtrl'
})
}
function initSession(user, $rootScope, loginService, userService) {
userService.getInitParams().then(function (envJson) {
// some code omitted here...
userService.openUserSession(envJson).then(function (data) {
var sessionID = data.data[0];
$rootScope.rageSessionVars.sessionID = sessionID;
$rootScope.rageSessionVars.userID = $rootScope.userID; // *** HOW TO SUSPEND ALL CODE UNTIL userID IS ASSIGNED ??? ***
console.log("sessionID = " + sessionID);
$rootScope.rageSessionVars.currDashboardName = "Default";
});
});
}
* MainCtrl controller code *
(function () {
'use strict';
angular.module('rage')
.controller('MainCtrl',
['$rootScope', '$scope', '$interval', '$window', '$state', '$location', 'widgetDefinitions',
'defaultWidgets', 'gadgetInitService', main]);
function main($rootScope, $scope, $interval, $window, $state, $location, widgetDefinitions, defaultWidgets, gadgetInitService, authUser) {
var main = this;
**** authUser IS NEVER DEFINED !!!
if ($scope.userID == undefined) { /// *** NOTHING WORKS HERE !!! ***
//$state.go('login');
//$location.url('index.html#/?login');
//return ;
}
if ($scope.userID == undefined) {
main.userName = "risk user";
}
else {
$scope.main.userName = $scope.userID;
}
}
})();
Edit:
I see your getting confused on the use of 'Resolve'.
It should be used when you want some data passed into the controller when it's being run. Not to run a function before initiating a controller.
This isn't what you really want in this situation.
It kinda depends on your authentication method, cookies/tokens etc.
Here is a similar method that I would follow.
Have a Login service which handles the following
-- Login/Logout of the user
-- Checks if the user is authenticated
In your Controller call your service to check if the user is logged in.
-- If the user is not logged in, then prompt login screen
-- If the user is logged in, then let the controller continue execution
How I handle it, is whenever the user makes an UnAuthorised request where the server returns a 401 response I call my LoginService to prompt the login screen again.
I use an authInterceptor in Angular to catch any 401 response from the server.
Here is a nice guide on it: http://onehungrymind.com/winning-http-interceptors-angularjs/
This allows you to write your Unauthorized handler in one place.
Hope that makes it a bit clearer.
Related
I've looked at similar questions but I can't seem to understand what I am missing. Basically, I have a service that gets data from the server, and I am trying to get that data into a controller through UI-Router's resolve property. However, after following numerous tutorials and documentations, I can't get the controller to find the data, so to speak. Everything comes up as undefined. I am hoping someone can help me understand what is happening. My code is below.
services.js
myServices.factory('SoundCloudService', ['$http', '$log', '$sce', function($http, $log, $sce) {
function getPlayerHtml() {
return $http.get('/get-site-data').then(function(oEmbed) {
return $sce.trustAsHtml(oEmbed.data.player);
});
};
function getSiteAbout() {
return $http.get('/get-site-data').then(function(oEmbed) {
return $sce.trustAsHtml(oEmbed.data.about);
});
}
function getAllTracks() {
return $http.get('/get-all-tracks').then(function(tracks) {
return JSON.parse(tracks.data);
});
};
function getAllPlaylists() {
return $http.get('/get-playlists').then(function(playlists) {
return JSON.parse(playlists.data);
})
};
function getPlaylist(pid) {
return $http.post('/get-playlist', pid, $http.defaults.headers.post).then(function(playlist) {
return playlist.data;
});
};
function getXMostTrendingFrom(x, playlist) {
var i, trending = [];
playlist.sort(function(a, b) { return b.playback_count - a.playback_count} );
for(i=0;i<x;i++) {
trending.push(all_tracks[i]);
}
return trending;
};
return {
getAllTracks: getAllTracks,
getAllPlaylists: getAllPlaylists,
getPlayerHtml: getPlayerHtml,
getSiteAbout: getSiteAbout,
getXMostTrendingFrom: getXMostTrendingFrom,
getPlaylist: getPlaylist,
};
}]);
app.js
myApp.config(['$stateProvider', '$urlRouterProvider', 'ngMetaProvider',
function($stateProvider, $urlRouterProvider, ngMetaProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('main', {
url: '',
template: '<ui-view/>',
abstract:true,
controller: 'MainController',
resolve: {
player: function(SoundCloudService) { return SoundCloudService.getPlayerHtml(); },
about: function(SoundCloudService) { return SoundCloudService.getSiteAbout(); },
}
})
.state('main.home', {
url: '/',
templateUrl: '../static/partials/home.html',
controller: 'IndexController',
})
.state('main.team', {
url: '/team',
templateUrl: '../static/partials/team.html',
controller: 'TeamController',
})
.state('main.contact', {
url: '/contact',
templateUrl: '../static/partials/contact.html',
controller: 'ContactController',
})
.state('main.resources', {
url: '/resources',
templateUrl: '../static/partials/resources.html',
controller: 'ResourcesController',
})
.state('main.listen-to', {
url: '/listen-to',
templateUrl: '../static/partials/listen-to.html',
controller: 'ListenController',
})
.state('main.listen-to.season', {
url: '/listen-to/:season',
templateUrl: '../static/partials/listen-to.season.html',
controller: 'ListenController',
})
.state('main.listen-to.season.episode', {
url: '/listen-to/:season/:episode',
templateUrl: '../static/partials/listen-to.season.episode.html',
controller: 'ListenController',
})
.state('main.read', {
url: '/read',
templateUrl: '../static/partials/read.html',
controller: 'ReadController',
})
.state('main.read.post', {
url: '/read/:post',
templateUrl: '../static/partials/read.post.html',
controller: 'ReadController',
})
}
]);
controller.js
myControllers.controller('MainController', ['$scope', '$log', 'PageTitleService',
function($scope, $log, PageTitleService, player) {
$log.log(player); /* This is always undefined */
}
]);
[UPDATE]
As pointed out by Hadi in the answer below, I placed player in the array, and the controller now looks like this:
skodenControllers.controller('MainController', ['$scope', '$log', '$sce', 'PageTitleService', 'player',
function($scope, $log, $sce, PageTitleService, player) {
$log.log(player);
}
]);
The console DOES show the data, but only after an error as such:
Error: [$injector:unpr]
http://errors.angularjs.org/1.3.2/$injector/unpr?p0=playerProvider%20%3C-%20player
at angular.js:38
at angular.js:3930
at Object.d [as get] (angular.js:4077)
at angular.js:3935
at d (angular.js:4077)
at Object.e [as invoke] (angular.js:4109)
at F.instance (angular.js:8356)
at angular.js:7608
at r (angular.js:347)
at I (angular.js:7607)
Hopefully someone can lead me in the right direction.
You forgot pass player into array. change to this
myControllers.controller('MainController', ['$scope', '$log',
'PageTitleService','player',
function($scope, $log, PageTitleService, player) {
$log.log(player); /* This is always undefined */
}
]);
As myServices and myControllers are both modules, ensure you add them as dependencies of myApp module.
// init myApp module
angular.module('myApp', ['myServices', 'myControllers']);
Edit
Some leads :
According to the documentation, when using ui-router nested views, child views (state name = main.xxx) must declare the parent state, so you must add parent: "main" or child views won't inherit resolved properties of main state controller
As siteDate is loaded asynchronously in SoundCloudService (services.js:23), you cannot be sure it will be available in your controllers which are loaded at the same time.
Instead, add a getSiteDate() method to SoundCloudService which returns a promise. siteData is then cached and immediately return by the promise.
For example :
/**
* #name getSiteData
* #description Scrap site data
* #returns {promise} a promise
*/
function getSiteData() {
var deferred = $q.defer();
if(siteData) {
deferred.resolve(siteData);
}
else {
$http.get('/get-site-data').then(function(response) {
siteData = response.data;
deferred.resolve(siteData);
}, function(err) {
deferred.reject(err.message);
});
}
return deferred.promise;
}
Why trying to map SoundCloudService to siteData ? You should simply inject SoundCloudService in controllers that use it :
For example :
skodenControllers.controller('MainController', ['$scope', '$log', '$sce', 'PageTitleService', 'SoundCloudService',
function($scope, $log, $sce, PageTitleService, SoundCloudService) {
// Note: getSiteData() could use a cache inside the service
SoundCloudService.getSiteData().then(function(siteData) {
...
});
}
I am using UI Router Tabs for navigation/routing. There are three tabs [User, Address and Service]. Individual tab have their own state, url, controller and template.
How can I pass request params (saved object ID, firstname and lastname) from User tab [UserCtrl.js] to AddressCtrl.js and ServiceCtrl.js.
HOW CAN I ACHIEVE THIS?
What I have done so far?
app.js
'use strict';
var app = angular.module('xyzApp', [ 'ui.router','ngResource', 'ui.bootstrap', 'ui.router.tabs']);
app
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', '$interpolateProvider', '$locationProvider',
function($stateProvider, $urlRouterProvider, $httpProvider, $interpolateProvider, $locationProvider) {
// CSRF Support
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$urlRouterProvider.otherwise('/');
$stateProvider
.state('new', {
url: '/',
controller: 'MainCtrl',
templateUrl: 'partials/base.html'
})
.state('new.user', {
url: '/new/user',
controller: 'UserCtrl',
templateUrl: 'partials/user-tab.html'
})
.state('new.address', {
url: '/new/address',
controller: 'AddressCtrl',
templateUrl: 'partials/address-tab.html'
})
.state('new.service', {
url: '/new/service',
controller: 'ServiceCtrl',
templateUrl: 'partials/service-tab.html',
})
}]);
MainCtrl.js
'use strict';
app
.controller('MainCtrl', ['$scope', function($scope) {
// Inititalise the new personnel page tabs
$scope.initialise = function() {
$scope.go = function(state) {
$state.go(state);
};
$scope.personneltabinfo = [
{
heading: 'User',
route: 'new.user',
//params: {
// userId: $scope.userId
//},
},
{
heading: 'Address',
route: 'new.address',
},
{
heading: 'Service',
route: 'new.service'
}
];
};
$scope.initialise();
}]);
UserCtrl.js
'use strict';
app
.controller('UserCtrl', ['$scope', '$rootScope', '$http', '$compile', '$timeout', 'userServices',
function($scope, $rootScope, $http, $compile, $timeout, userServices) {
$scope.userId = '';
$scope.savePerson = function() {
console.log("This is userData:" + $scope.userData);
// user resource service
userServices.addUser($scope.userData)
.then(function(data) {
// pass $scope.userId to [Account and Service] routes view.
// pass firstname and secondname value to [Account and Service] routes view so it’s value can be shown on their panel-title.
$scope.userId = data.id;
toastr.success('Personnel ' + $scope.baseData.first_name + ' record saved into database');
}, function(data, status, headers, config) {
toastr.error('Field Error:' + " Please fill all fields mark with * ");
});
};
}]);
I'm angular beginner!
If I understand your question, you want to pass the data from a controller to another controller.
You can check on: Passing data between controllers in Angular JS?
You can use :
a service: https://docs.angularjs.org/guide/services,
$rootScope but that's not the best solution,
or the binding data between components: https://docs.angularjs.org/guide/component
Right now i am making an AngularJS+UI router install application. But i have a problem, the problem is, that i want to disable access to the views, associated with the install application. I want to do it in resolve in the state config.
But the problem is i need to get the data from a RESTful API, whether the application is installed or not. I tried making the function, but it loaded the state before the $http.get request was finished.
Here was my code for the resolve function:
(function() {
var app = angular.module('states', []);
app.run(['$rootScope', '$http', function($rootScope, $http) {
$rootScope.$on('$stateChangeStart', function() {
$http.get('/api/v1/getSetupStatus').success(function(res) {
$rootScope.setupdb = res.db_setup;
$rootScope.setupuser = res.user_setup;
});
});
}]);
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/404");
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
data: function($q, $state, $timeout, $rootScope) {
var setupStatus = $rootScope.setupdb;
var deferred = $q.defer();
$timeout(function() {
if (setupStatus === true) {
$state.go('setup-done');
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}
}
})
.state('user-registration', {
url: "/install/user-registration",
templateUrl: "admin/js/partials/user-registration.html",
controller: "RegisterController"
})
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html"
})
.state('404', {
url: "/404",
templateUrl: "admin/js/partials/404.html"
});
}]);
})();
EDIT:
Here is what my ajax call returns:
Try this way:
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
setupStatus: function($q, $state, $http) {
return $http.get('/api/v1/getSetupStatus').then(function(res) {
if (res.db_setup === true) {
$state.go('setup-done');
return $q.reject();
}
return res;
});
}
}
})
Then inject setupStatus in controller:
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html",
controller: ['$scope', 'setupStatus', function ($scope, setupStatus) {
$scope.setupdb = setupStatus.db_setup;
$scope.setupuser = setupStatus.user_setup;
}]
})
I'm using Devise for authenticatication in my Rails and Angular app. I'm trying to do a conditional statement on one of the states based on whether a user is authenticated.
I'm using the onEnter callback function to determine if a user is authenticated.
routes:
// Dashboard state
.state('dashboard', {
url: '/dashboard',
templateUrl: 'dashboard.html',
controller: 'MainCtrl',
})
// Login state
.state('login', {
url: '/login',
templateUrl: '_login.html',
controller: 'AuthCtrl',
onEnter: ['$state', 'Auth', function($state, Auth) {
Auth.currentUser().then(function (){
$state.go('dashboard');
})
}]
})
// Register state
.state('register', {
url: '/register',
templateUrl: '_register.html',
controller: 'AuthCtrl',
onEnter: ['$state', 'Auth', function($state, Auth) {
Auth.currentUser().then(function (){
$state.go('dashboard');
})
}]
})
$urlRouterProvider.otherwise('dashboard');
Nav controller
// NAV controller
// ------------------------------
.controller('NavCtrl', ['$scope', 'Auth',
// Main scope (used in views)
function($scope, Auth) {
$scope.signedIn = Auth.isAuthenticated;
$scope.logout = Auth.logout;
Auth.currentUser().then(function (user){
$scope.user = user;
});
$scope.$on('devise:new-registration', function (e, user){
$scope.user = user;
});
$scope.$on('devise:login', function (e, user){
$scope.user = user;
});
$scope.$on('devise:logout', function (e, user){
$scope.user = {};
});
}
])
Authentification controller
// Authentification controller
// ------------------------------
.controller('AuthCtrl', ['$scope', '$state', 'Auth',
// Main scope (used in views)
function($scope, $state, Auth) {
$scope.login = function() {
Auth.login($scope.user).then(function(){
$state.go('home');
});
};
$scope.register = function() {
Auth.register($scope.user).then(function(){
$state.go('home');
});
};
}
]);
How can I determine a non-authenticated user on the dashboard state, and redirect them to login?
You have have a few options to go down for this sort of thing:
1) In MainCtrl add an init() function which checks if(!Auth.isAuthenticated) $state.go('login'). Downside is if they lose auth after the controller is instantiated this does not redirect them.
2) I presume you're making some kind of back-end calls which require Authentication. If so, if they're not authed you can catch the 401 response via an http interceptor and redirect them to login there. This is the method I always use.
3) Attach a $watch to Auth.isAuthenticated do the redirect from there
I am trying to follow this example to show a bootstrap modal on a certain state. It works fine without a modal (so the state config should be ok). All needed dependencies (ie angular bootstrap) should be available.
when I do a console.debug($stateParams) before $modal.open I get the correct data, within the $modal.open-method however the stateParams from the last state are returned (the state I am coming from)
Any hints?
EDIT
the relevant state cfg:
.state('publications.view', {
parent: 'publications.productSelection',
url: '/{productSlug:[a-zA-Z0-9-]+}/{docID:[0-9]+}_{slug:[a-zA-Z0-9-]+}',
onEnter: ['restFactory', '$state', '$stateParams', '$modal',
function(restFactory, $state, $stateParams, $modal) {
console.debug($stateParams.docID);
$modal.open({
templateUrl: 'partials/publication.html',
resolve: {
publication: ['restFactory', '$stateParams',
function(restFactory, $stateParams) {
console.debug($state.params);
console.debug($stateParams);
return restFactory.view($stateParams.language, $stateParams.productSlug, $stateParams.docID);
}
]
},
controller: ['$scope', '$sce', 'publication', '$rootScope',
function($scope, $sce, publication, $rootScope) {
$rootScope.pageTitle = publication.data.data.publication.Publication.title;
$scope.publication = $sce.trustAsHtml(publication.data.data.publication.Publication.content);
}
]
});
}
]
});
You can get around this issue by injecting the current $stateParams into the onEnter function, save them as state in some service, and inject that service instead into your modal resolves.
I am adapting the code from here: Using ui-router with Bootstrap-ui modal
.provider('modalState', function($stateProvider) {
var modalState = {
stateParams: {},
};
this.$get = function() {
return modalState;
};
this.state = function(stateName, options) {
var modalInstance;
$stateProvider.state(stateName, {
url: options.url,
onEnter: function($modal, $state, $stateParams) {
modalState.stateParams = $stateParams;
modalInstance = $modal.open(options);
modalInstance.result['finally'](function() {
modalInstance = null;
if ($state.$current.name === stateName) {
$state.go('^');
}
});
},
onExit: function() {
if (modalInstance) {
modalInstance.close();
}
}
});
};
})
Then in your app config section
.config(function($stateProvider, $urlRouterProvider, modalStateProvider) {
modalStateProvider.state('parent.child', {
url: '/{id:[0-9]+}',
templateUrl: 'views/child.html',
controller: 'ChildCtrl',
resolve: {
role: function(Resource, modalState) {
return Resource.get({id: modalState.stateParams.id}).$promise.then(function(data) {
return data;
});
}
}
});
}