I need to initialize my Angular with some User data that the whole app depends on. Therefore I need the initialization to be resolved before the router kicks in and controllers are initialized.
Currently, I wrote the initialization code in a run() block of the angular module. The initialization involves an asynchronous http request to get user data and the rest of the application relies upon the user data.
How can I ensure that the http request is resolved before the router kicks-in initializing the controllers?
I am using the ui-router.
The initialization consists in the following:
1) get cookie 'userId'
2) get User from server (asynchronous http request, the whole app depends upon the User)
3) set authService.currentUser
this is a sample of the code
.run(['$cookies', 'userApiService', 'authService',
function($cookies, userApiService, authService){
var userId = $cookies.get('userId');
userId = parseCookieValue(userId);
userApiService.getOne(userId).then(function(user){
authService.currentUser = user;
});
}])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider',
function($stateProvider, $urlRouterProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$urlRouterProvider.when('/', '/main');
$stateProvider
.state('login', {
url: '/login',
views: {
'header': {
templateUrl: 'views/header.html',
controller: 'HeaderCtrl'
},
'content': {
templateUrl: 'views/login.html',
controller: 'LoginCtrl'
},
'footer': {
templateUrl: 'views/footer.html',
}
}
})
.state('main', {
url: '/main',
views: {
'content#': {
template: '',
controller: function($state, authService) {
if(!authService.isAuthenticated()) {
$state.go('login');
}
if(authService.isStudent()) {
$state.go('student');
}
if(authService.isAdmin()) {
$state.go('admin');
}
}
}
}
})
.state('student', {
url: '/student',
views: {
'header#': {
templateUrl: 'views/header.html',
controller: 'HeaderCtrl'
},
'content#': {
templateUrl: 'views/student.html',
controller: 'StudentCtrl'
},
'footer#': {
templateUrl: 'views/footer.html',
}
}
})
.state('admin', {
url: '/admin',
views: {
'header#': {
templateUrl: 'views/header.html',
controller: 'HeaderCtrl'
},
'content#': {
templateUrl: 'views/admin.html',
controller: 'AdminCtrl'
},
'footer#': {
templateUrl: 'views/footer.html',
}
}
})
}])
Expanding on someone's comment, you can create a root state that is a parent to all of your other app's states (children to the root). The root state resolves all the user data and then you can inject the user data to any controller or store it in a service.
$stateProvider
.state('root', {
url: '',
abstract: true,
template: '', // some template with header, content, footer ui-views
resolve: {
// fetch user data
}
})
.state('root.login', {
url: '/login',
views: {
'header': {
templateUrl: 'views/header.html',
controller: 'HeaderCtrl'
},
'content': {
templateUrl: 'views/login.html',
controller: 'LoginCtrl'
},
'footer': {
templateUrl: 'views/footer.html',
}
}
})
.state('root.main', {
url: '/main',
views: {
'content#': {
template: '',
controller: function($state, authService) {
if(!authService.isAuthenticated()) {
$state.go('login');
}
if(authService.isStudent()) {
$state.go('student');
}
if(authService.isAdmin()) {
$state.go('admin');
}
}
}
}
})
... // your other states
The key is that all of your app states must be a child of your root state i.e. root.<name> in your state declaration. This will ensure that no other controller starts until your user data is available. For more information on resolve and how to use it read here. Also, parent and child states.
Related
In my app I am trying to dashboard.html and associated controller would load if the condition is true, but the problem is first time it will load the controller but from second time it is opening html page but not only loading controller.
What may the wrong where I did mistake please help me to find.
app.js
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AutoLoginCtrl'
})
.state('app.autoLog', {
url: '/autoLog',
controller: 'AutoLoginCtrl'
})
.state('app.search', {
url: '/search',
views: {
'menuContent': {
templateUrl: 'templates/search.html'
}
}
})
.state('app.browse', {
url: '/browse',
views: {
'menuContent': {
templateUrl: 'templates/browse.html'
}
}
})
.state('app.profileInfo', {
url: '/profileInfo/:success',
views: {
'menuContent': {
templateUrl: 'templates/profileInfo.html',
controller: 'ProfileCtrl'
}
}
})
.state('app.profile', {
url: '/profile/:data',
views: {
'menuContent': {
templateUrl: 'templates/profile.html',
controller: 'ProfileCtrl'
}
}
})
.state('app.dashboard', {
url: '/dashboard',
views: {
'menuContent': {
templateUrl: 'templates/dashboard.html'
}
}
})
.state('app.dog', {
url: '/dog',
views: {
'menuContent': {
templateUrl: 'templates/dog.html',
controller: 'ProfileCtrl'
}
}
});
$urlRouterProvider.otherwise('/app/autoLog');
loginController.js
If the below condition is true then is will call .state('app.dashboard')
if (data[0].Userprofile1 == true && data[0].Userprofile2 == true) {
$ionicLoading.hide();
console.log('My profile setup is over');
$state.go("app.dashboard");
}
dashboardController.js
app.controller('dashboardCtrl', function($scope, $http, $state, UserService) {
$scope.pageName = 'Hi i am user';
console.log('Hi home controller loading')
var userStatus = UserService.getUser()
console.log(userStatus);
if (userStatus != null) {
$scope.ersrserer = userStatus.userID;
console.log($scope.ersrserer);
} else {
console.log('its empty');
}
})
first time when it calls dashboard.html it will load controller also but from second time it will not load controller, what went wrong here?
A state requires a controller to be initialized with it, you have add the controller and inside the state you can add a resolve block:
.state('your state', {
url: '/url',
resolve: {
someName: function(inject here) {
//return something, or redirect to a state...etc
}
},
templateUrl: 'templates/something.html',
controller: 'ExampleCtrl'
})
I know that this has been asked before (here and here among others), but I just can't understand what is the problem with my code. Here it is:
(function () {
'use strict';
angular
.module('app.students', ['common', 'xeditable', "googlechart", 'iosDblclick', 'anguFixedHeaderTable', 'disableAll'])
.config(config);
/** #ngInject */
function config($stateProvider, $translatePartialLoaderProvider) {
// State
$stateProvider
.state('app.students', {
abstract: true,
url: '/students'
})
.state('app.students.studentList', {
url: '/',
views: {
'content#app': {
templateUrl: 'app/main/students/views/studentList/studentList.html',
controller: 'StudentListController as vm'
}
}
})
.state('app.studentsInProcess.studentList', {
url: '/process/:id',
views: {
'content#app': {
templateUrl: 'app/main/students/views/studentList/studentList.html',
controller: 'StudentListController as vm'
}
},
parent: 'app.students'
})
.state('app.students.studentDetails', {
url: '/:id',
views: {
'content#app': {
templateUrl: 'app/main/students/views/studentDetails/studentDetails.html',
controller: 'StudentDetailsController as vm'
}
}
});
// Translation
$translatePartialLoaderProvider.addPart('app/main/students');
}
})();
When I click on a link with a ui-sref of "app.studentsInProcess.studentList" i Get the error
"Error: Could not resolve 'app.studentsInProcess.studentList' from state 'app'"
What am I doing wrong? Please bear with me as this stuff is new to me.
I have an angular project. In that if i type in the url as "dashboard" it is redirecting to "dashboard.html" page. But i want to restrict user going into that page without successfully logged In.
Below in my routing code please check.
index.js
angular.module('adminsuite',['ui.router','ngCookies']).config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('login', {
url: '/',
views:{
pageContent:{
templateUrl: 'Login/login.html',
controller: 'loginController'
},
footer:{
templateUrl: 'common/footer.html',
controller: 'footerController'
}
}
})
// HOME STATES AND NESTED VIEWS ========================================
.state('dashboard', {
url: '/dashboard',
views:{
header:{
templateUrl: 'common/header.html',
controller: 'headerController'
},
pageContent:{
templateUrl: 'dashboard/dashboard.html',
controller: 'dashboardController'
},
footer:{
templateUrl: 'common/footer.html',
controller: 'footerController'
}
}
})
//SURVEY STATES
.state('survey', {
url: '/survey',
views:{
header:{
templateUrl: 'common/headerTool.html',
controller: 'headerController'
},
pageContent:{
templateUrl: 'survey/survey.html',
controller: 'surveyController'
},
footer:{
templateUrl: 'common/footer.html',
controller: 'footerController'
}
}
});
// ABOUT PAGE AND MULTIPLE NAMED VIEWS =================================
});
in your loginController store value in $rootscope if user sucessfully logged in.
if(userloggedin == true )
{
$rootscope.loggedinuser = true
}
in your dashboardController get value from $rootscope and check if $rootscope.loggedinuser is equal to true then dasboard.html page will show otherwise page redirect to login page.
in your dashboardController
if($rootscope.loggedinuser !=true)
{
$state.go('login');
}
You can do one thing. In your loginController create a variable and store that in local storage if the user successfully logged in. And in your dashboard routing statement resolve that data from local storage. The page will load to /dashboard only if it can resolve that data. If he is not logged in the resolving data will be undefined
.state('dashboard', {
url: '/dashboard',
views:{
header:{
templateUrl: 'common/header.html',
controller: 'headerController'
},
pageContent:{
templateUrl: 'dashboard/dashboard.html',
controller: 'dashboardController'
},
footer:{
templateUrl: 'common/footer.html',
controller: 'footerController'
}
},
resolve: {
accessToken: ['$localStorage', function($localStorage){
return $localStorage.accessToken
}]
}
})
OR your resolve code can be like
resolve: {
accessToken: ['$localStorage','$state', function($localStorage, $state){
if($localStorage.accessToken)
return $localStorage.accessToken
else{
$state.go(login)
return;
}
}]
}
here variable accessToken is storing to $localStorage in your loginController as
if('successfully logged in'){
$localStorage.accessToken = 'a random value';
}
You can use sessionStorage, $cookieStore etc. instead of $localStorage. It's option of you.
There is one more option. Broadcasting a variable when login happens and resolve that data in dashboard
index.html
--navbar.html
--content.html
--customer.html
--netScore.html
--useExp.html
--useExpNest1.html
--useExpNest2.html
--internalPerformance.html
--leftNavPanel.html
I have this kind of view structure and I want to load them all at once so I'm planning to put this in a single state. I saw this answer but it seems that its only applicable for a simple/double nested views(I have 3 or more nested views). How can I put this in a single state, or is there a better way if not possible?
EDIT
I've come up with this solution and it works somehow.
.state('index', {
url: '/',
views: {
'': {
templateUrl: 'app/modules/bulletin/views/index.view.html',
controller: 'indexController'
},
'navbar#index': {
templateUrl: 'app/modules/bulletin/views/index/navbar.view.html',
controller: 'navbarController'
},
'content#index': {
templateUrl: 'app/modules/bulletin/views/index/content.view.html',
controller: 'contentController'
},
'leftNavPanel#index': {
templateUrl: 'app/modules/bulletin/views/index/leftNavPanel.view.html',
controller: 'contentController'
}
}
})
.state('index.content', {
views: {
'customer#index': {
templateUrl: 'app/modules/bulletin/views/index/content/customer.view.html'
},
'internalPerformance#index': {
templateUrl: 'app/modules/bulletin/views/index/content/internalPerformance.view.html'
}
}
})
.state('index.content.customer', {
views: {
'netScore#index.content': {
templateUrl: 'app/modules/bulletin/views/index/content/customer/netScore.view.html'
},
'useExp#index.content': {
templateUrl: 'app/modules/bulletin/views/index/content/customer/useExp.view.html'
}
}
})
.state('index.content.customer.useExp', {
views: {
'useExpNest1#index.content.customer': {
templateUrl: 'app/modules/bulletin/views/index/content/customer/useExp/useExpNest1.view.html'
},
'useExpNest2#index.content.customer': {
templateUrl: 'app/modules/bulletin/views/index/content/customer/useExp/useExpNest2.view.html'
}
}
})
And then add this code to the indexController(most parent controller)
$state.go('index.content');
$state.go('index.content.customer');
$state.go('index.content.customer.useExp');
But this answer is still wrong because, let's say that netScore.html has some child views, we will create route for it then go to that state, but netScore and useExp states are on the same level so only one of them will be loaded if we use
$state.go('index.content');
$state.go('index.content.customer');
$state.go('index.content.customer.netScore');
$state.go('index.content.customer.useExp');
EDIT 2
Here's a plunker of what I've done so far. The view names are slightly different but you will see clearly the problem there
You can use a combination of named views plus abstract: true property to load child views by default
angular.module('sampleModule', [
'ui.router'
]);
angular.module('sampleModule')
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.when('','/');
$stateProvider
.state('main', {
url: '',
abstract: true,
templateUrl: 'main.view.html'
})
.state('main.load', {
url: '/',
abstract: true,
views:{
'content':{
templateUrl:'content.view.html',
},
'navbar':{
templateUrl:'navbar.view.html',
}
}
})
.state('main.load.customer', {
url: '',
abstract: true,
views:{
'customerPerception':{
templateUrl:'content-customerPerception.view.html'
},
'customerExperience':{
templateUrl:'content-customerExperience.view.html'
}
}
})
.state('main.load.customer.netTrustScore', {
url: '',
abstract: true,
views: {
'netTrustScore': {
templateUrl: 'content-customerPerception-netTrustScore.view.html'
},
'useExperience': {
templateUrl: 'content-customerPerception-useExperience.view.html'
},
'trustStatements': {
templateUrl: 'content-customerPerception-trustStatements.view.html'
}
}
})
.state('main.load.customer.netTrustScore.somethingElse', {
url: '',
views: {
'abc': {
templateUrl: 'content-customerExperience-customerComplaints.view.html'
},
'': {
templateUrl: 'content-customerExperience-networkQualityIndex.view.html'
}
}
})
;
}])
.controller('mainController', ['$scope', '$state', function($scope, $state) {
console.log('mainController initialized!');
}]);
here's a plnkr
https://plnkr.co/edit/BBAeWjnGbTsbO1lMguU9?p=preview
Thanks to the guys from AngularJS group in FB. The problem is I put two sibling views in two different states. UI router cant load two states at the same time. So the solution is to put all same level views in a single subState.
Lets assume we have this kind of structure:
index.html
--navbar.html
--content.html
--customer.html
--netScore.html
--netScoreNest1.html
--netScoreNest2.html
--useExp.html
--useExpNest1.html
--useExpNest2.html
--internalPerformance.html
--leftNavPanel.html
the proper routing for this would be like this
.state('index', {
url: '/',
views: {
'': {
templateUrl: 'index.view.html',
controller: 'mainController'
},
'navbar#main': {
templateUrl: 'index/navbar.view.html'
},
'content#main': {
templateUrl: 'index/content.view.html'
},
'leftNavPanel#main': {
templateUrl: 'index/leftNavPanel.view.html'
}
}
})
.state('index.subLevel', {
views: {
'customer#index': {
templateUrl: 'index/content/customer.view.html'
},
'internalPerformance#index': {
templateUrl: 'index/content/internalPerformance.view.html'
}
// IF LEFTNAVPANEL OR NAVBAR HAVE SUB VIEWS, PUT IT HERE
}
})
.state('index.subLevel.subLevel2', {
views: {
'netScore#index.subLevel': {
templateUrl: 'index/content/customer/netScore.view.html'
},
'useExp#index.subLevel': {
templateUrl: 'index/content/customer/useExp.view.html'
}
// IF INTERNALPERFORMANCE HAVE SUB VIEWS, PUT IT HERE
}
})
.state('index.subLevel.subLevel2.subLevel3', {
views: {
'netScoreNest1#index.subLevel.subLevel2': {
templateUrl: 'index/content/customer/netScore/netScoreNest1.view.html'
},
'netScoreNest2#index.subLevel.subLevel2': {
templateUrl: 'index/content/customer/netScore/netScoreNest2.view.html'
},
'useExpNest1#index.subLevel.subLevel2': {
templateUrl: 'index/content/customer/useExp/useExpNest1.view.html'
},
'useExpNest2#index.subLevel.subLevel2': {
templateUrl: 'index/content/customer/useExp/useExpNest2.view.html'
}
}
})
And then in mainController, load the inner most child state, this will automatically load the views of all its parent(up to topmost parent state 'index')
$state.go('index.subLevel.subLevel2.subLevel3');
And thats it. And also here's a plunker to make it easier to understand. (Views and structure are slightly different from this post different. Too lazy to edit)
In Angular/Ionic i have three different states:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('layout', {
url: "",
abstract: true,
templateUrl: "templates/base/layout.html"
})
.state('layout.home', {
url: "/home",
views: {
'mainContent' :{
templateUrl: "templates/home.html"
},
'leftMenuContent' :{
controller: "LeftMenuController",
templateUrl: "templates/left-menu.html"
}
}
})
.state('layout.about', {
url: "/about",
views: {
'mainContent' :{
templateUrl: "templates/about.html",
controller: "AboutController"
},
'leftMenuContent' :{
controller: "LeftMenuController",
templateUrl: "templates/left-menu.html"
}
}
})
So i have an abstract state which defines the base/main layout, home and about states which have two views: mainContent and leftMenuContent. As for the LeftMenuController functionality, it just returns custom options for every route depending the $state.current.name.
As leftMenuContent will be the same for every state, is there any way to avoid defining it in every new state and inheriting it from the layout state (or somewhere else) instead?
You are almost there: By calling the state layout.home it will inherit its views from layout, so this should work:
.state('layout', {
url: "",
abstract: true,
templateUrl: "templates/base/layout.html",
views: {
'leftMenuContent' :{
controller: "LeftMenuController",
templateUrl: "templates/left-menu.html"
}
}
})
.state('layout.home', {
url: "/home",
views: {
'mainContent' :{
templateUrl: "templates/home.html"
}
}
})
.state('layout.about', {
url: "/about",
views: {
'mainContent' :{
templateUrl: "templates/about.html",
controller: "AboutController"
}
}
})
This is what I found to be working to show nested views.
So, I have a main view in which I display my home template (<ui-view> in index.html), in the home template I have another <ui-view> in which I call different states based on what I want.
config(function config($stateProvider) {
$stateProvider
.state('sidemenu.home', {
url: '/home',
views: {
'topView': {
controller: 'HomeCtrl',
templateUrl: 'home/home.tpl.html'
}
}
})
.state('sidemenu.home.list', {
templateUrl: 'home/home-list.tpl.html'
/*,controller: 'HomeCtrl'*/
})
.state('sidemenu.home.login', {
templateUrl: 'home/home-login.tpl.html'
/*,controller: 'HomeCtrl'*/
})
.state('sidemenu.home.register', {
templateUrl: 'home/home-register.tpl.html'
/*,controller: 'HomeCtrl'*/
})
.state('sidemenu.home.coach', {
templateUrl: 'home/home-coach.tpl.html'
/*,controller: 'HomeCtrl'*/
})
;
})
.controller('HomeCtrl', function HomeController($scope, $rootScope, $state) {
/**
* ===== FIRST LOGIC =====
**/
//Redirect the user to the appropriate page.
switch ($rootScope.tellState()) {
case "ok-local":
$state.transitionTo('sidemenu.home.list');
break;
case "register":
$state.transitionTo('sidemenu.home.register');
break;
case "login":
$state.transitionTo('sidemenu.home.login');
break;
case "coach":
$state.transitionTo('sidemenu.home.coach');
break;
}