Working with multiple states and single controller in angular.ui-router - angularjs

I am learning angularJS and creating a web application which uses ui-router.
I have defined states as follows:
angular.module('test', [])
.config(function($stateProvider){
$stateProvider.
state('root',{
url: '/',
abstract:true,
templateUrl: '/root.html',
controller: 'MyController'
})
.state('root.route1',{
url: '/route1',
parent: 'root',
views:{
'':{
templateUrl: '/route1.html'
}
'estimatedCost#':{
templateUrl: '/esitmatedCost.html'
}
}
})
.state('root.route2',{
url: '/route2',
parent: 'root',
views:{
'':{
templateUrl: '/route2.html'
}
'estimatedCost#':{
templateUrl: '/esitmatedCost.html'
}
}
})
});
While navigating back and forth between route1 and route2, I want to share scope variables from MyController. When I navigate to route2 from route1, it is loosing value of scope variable.
I am not sure what I am doing wrong.
Can anyone help me?
Thanks in advance.

I have yet to work with the ui-router, but I have worked with AngularJS for the last couple of years and this is how the language generally has worked in the past.
A controller's main purpose is to control the data on a single template. These controllers can communicate to each other through an AngularJS factory, often known as a service. In your case, you probably want to use a service as the controllers are getting destroyed on successful route change.
angular.module('test', [])
.factory('myFactory', function() {
var info = "Hello World";
return {
info: info
};
})
.controller('CtrlOne', function($scope, myFactory) {
$scope.info = myFactory.info;
})
.controller('CtrlTwo', function($scope, myFactory) {
$scope.info = myFacotry.info;
});
You can then use the two controllers on the two different views and they share the variables from the service that connects them.

Use $stateParams to pass parameters between two states.
Fallow the below steps :
Define your state with params object.
.state('route.route1', {
url: 'your url name',
params: {
nameOfParamObj: null
},
controller: 'your controller',
templateUrl: 'your template url',
})
From the controller where you want to send scope data use as fallows
$state.go(toState, params, options);
In toState controller catch state params using $stateParams
$stateParams.yourParamObjectName
Make sure $stateParams, $state services as dependency in your regarding controller
Have a look into the official ui-router documentation Here.

Related

angular ui-router state how is controller being called

I have see people post things online about using Angular ui-router and i see these types are code that do not explicitly even show an controller name, so how is it even suppose to know how to call the controller?
state('new-rp', {
url: '/new/:portfolioId',
templateUrl: 'new.html',
controller: function($scope, $stateParams) {
$scope.portfolioId = $stateParams.portfolioId;
}
})
Code of mine that I TRIED that does NOT list the controller like above
.state("deviceDetail", {
url: "/devices/:DeviceId", // param is required which specific device id
templateUrl: "app/devices/deviceDetailView.html", // ui elements
controller: function($scope,$stateParams) {
$scope.DeviceId = $stateParams.DeviceId;
}
});
Problem is the controller is NOT hit
But this code explicitly uses the controller name and the controller gets hit, thus i'm having trouble with how this type of code would hit a controller
controller: function($scope,$stateParams) {
$scope.DeviceId = $stateParams.DeviceId;
}
HOW does it know? (doesn't work for me )
You can declare controllers in two ways in UI router states:
//1
.state('new-rp', {
url: '/new/:portfolioId',
templateUrl: 'new.html',
controller: function($scope, $stateParams) {
$scope.portfolioId = $stateParams.portfolioId;
}
})
//2
.state('new-rp', {
url: '/new/:portfolioId',
templateUrl: 'new.html',
controller: PortfolioController
})
.
.
.
//PortfolioController definition
In method #1, your controller is created for you inline, while in #2, UI Router is looking for an explicitly declared controller called PortfolioController

Passing parent scope variables to ui-view component

I'm using angular-ui/ui-router and have directives/components with isolate scope defined to be rendered on state change.
$stateProvider
.state('about', {
url: '/about',
template: '<about></about>'
})
.state('blog', {
url: '/blog',
template: '<blog></blog>'
})
My main page is a directive that loads the user with all permissions in its controller
app.directive('main', function() {
return {
...
template: "<div><header></header><div ui-view></div></div>",
controller: [..., function(...) {
$scope.user = // $http load user
}]
);
When navigating to different states, I want the user to be passed to all ui-view directives/components. However I can't find a nice way to realize this without a resolve that would load the user on every state change or using the $rootScope
I'm looking for something similar to the following, where user is the one from the parent scope.
$stateProvider
.state('about', {
url: '/about',
template: '<about user="user"></about>'
})
.state('blog', {
url: '/blog',
template: '<blog user="user"></blog>'
})
Any idea how this can be done?
in the controller, you can assign the user to $rootScope, like $rootScope.user = sth, then you can whether add controller for each state url to get the user like $scope.user= $rootScope.user, or pass the rootScope user as parameter.
you need to use $rootscope in your controller like this
$rootScope.user = sth;
and you can get user like this
$scope.user= $rootScope.user;

Inject a resolve provider from child state to controller

So i'm trying to pull some id from the url parameters.
Here are my states :
$stateProvider
.state('parent', {
url: '/parent',
templateUrl: 'path/to/parent.html',
controller: 'lolController'
})
.state('parent.child', {
url: '/child-profile/:id',
templateUrl: 'path/to/child.html',
resolve: {
someId: function ($stateParams) {
// I cant print the id from here
console.log("PARAMS", $stateParams.id)
return $stateParams.id;
}
},
})
Controller
.controller('lolController',
['$scope', 'someId', function ($scope, someId) {
$scope.someId = someId;
}])
But whenever i'm trying to access the url /parent/child-profile/123abc i'm getting the error Unknown provider: someId See error here..
How do I fix this? Thanks.
EDIT
The answer provided by Jay Shukla helped me get this idea.
The parameter is undefined because I declared the controller on the parent state before actually calling the child state which contains the value from it's url. Here's a simple solution I came up with, with Jay Shukla's help.
$stateProvider
.state('parent', {
url: '/parent',
templateUrl: 'path/to/parent.html',
})
.state('parent.child', {
url: '/child-profile/:id',
templateUrl: 'path/to/child.html',
controller: 'lolController'
})
I removed the controller declaration from the parent state and moved it to the child state. Since in my situation the parent state's template only contains a <div ui-view></div>.
The idea is :
Only state is nested but both html are separate so controller models will not be inherited - Jay Shukla
Each state can have their own controller.
Please add/edit more to improve this question.
Try to inject $stateParams then you will get id in that object.
Like this
.controller('lolController',
['$scope', '$stateParams', function ($scope, $stateParams) {
$scope.someId = $stateParams.id;
}])
You can also defined your parameters in different ways as below
url: '/child-profile/:id', // Inside stateparams you will get id as key
OR
url: '/child-profile/{id}',
OR
url: '/child-profile/:{id:int}', // Id with integer value

Angular ui.router. Deep nested routes

Here is an example to check http://embed.plnkr.co/uVMlkk/preview
When we navigate to 'page2' route there is a 'hey, I'm a subroute' note.
But once we navigate anywhere else that note will disappear forever.
The goal is to make some nested states to be shown right away (as a default ones).
I assume there should be some cases using $state.go(), but can't figure it out so far. Any help is highly appreciated.
State definition snippet:
.state('root.page2.tab', {
url: '/:tabId',
templateUrl: 'tpl.page2.tab.html',
controller: 'Page2TabController'
})
.state('root.page2.tab.subroute', {
url: '',
templateUrl: 'tpl.page2.tab.subroute.html'
})
the content of the 'tpl.page2.tab.subroute.html':
hey, I'm a subroute
related controller:
.controller('Page2TabController', ['$scope', '$state', function($scope, $state) {
$scope.tabId = $state.params.tabId;
$state.go('root.page2.tab.subroute');
}])
There is a fixed version.
I removed the url from the 'root.page2.tab.subroute'
.state('root.page2.tab.subroute', {
//url: '',
templateUrl: 'tpl.page2.tab.subroute.html'
})
And because the parent has defined paramater tabId:
.state('root.page2.tab', {
url: '/:tabId',
templateUrl: 'tpl.page2.tab.html',
controller: 'Page2TabController'
})
We have to pass that param inside of the redicrection:
.controller('Page2TabController', ['$scope', '$state', function($scope, $state) {
$scope.tabId = $state.params.tabId;
// instead of this
// $state.go('root.page2.tab.subroute');
// we need this
$state.go('root.page2.tab.subroute', $state.params);
}])
Check the working, fixed version here
ANOTHER approach - using redirectTo - there is a working plunker
One way, inspired by this:
Redirect a state to default substate with UI-Router in AngularJS
could be to add a very smart but small redirect code snippet:
.run(['$rootScope', '$state', function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(evt, to, params) {
if (to.redirectTo) {
evt.preventDefault();
$state.go(to.redirectTo, params)
}
});
}])
And adjust our state like this:
.state('root.page2.tab', {
url: '/:tabId',
templateUrl: 'tpl.page2.tab.html',
controller: 'Page2TabController',
redirectTo: 'root.page2.tab.subroute',
})
Check it here
There is a trick how to handle scenarios:
Parent should trigger some action in case that
it is accessed, or
its reached again, when navigating back from child in a parent state
In that case, we can use the "target (ui-view) for a child" as a place where sits the special view, with special controller. This will be
injected into that position once parent is created and
re-injected into that position again, once child is left. In that case, it will be re-init.
Enough explanation. There is a working plunker. There is adjusted state:
.state('root.page2', {
url: '/page2',
views: {
'content#root': {
templateUrl: './tpl.page2.html',
controller: 'Page2Controller'
},
'#root.page2': {
template: '<div></div>',
controller: 'RedirectorController'
}
}
})
So, now we can do some magic inside of our 'RedirectorController'
.controller('RedirectorController', ['$scope', '$state',
function($scope, $state) {
$state.go('root.page2.tab', { tabId: $scope.activeTabId });
}])
Check it in action here
Read more about what that new view/controller get from the other (Scope Inheritance by View Hierarchy Only) one here
Nested states or views for layout with leftbar in ui-router?
How do I share $scope data between states in angularjs ui-router?

Most optimize way to load datas to display on router's controller

I need to build a User that can be the resut of different REST API call (each way comes from a specific route).
Let's say for this example that we can visit:
http://myapp/#user/:pseudo
http://myapp/#user/:user_id
angular.module('Test').config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/user/:pseudo', {
templateUrl: 'views/user.html',
controller: 'userFromPseudoCtrl'
}).
when('/user/:user_id', {
templateUrl: 'views/user.html',
controller: 'userFromIdCtrl'
}).
otherwise({
redirectTo: '/'
});
}
]);
then, i have 3 different controllers:
userFromPseudoCtrl
userFromIdCtrl
userCtrl (To control the view)
angular.module('Test').controller('userFromPseudoCtrl', function($User, $http) {
$http.get('/getUserFromPseudo/test')
.success(function(User) {
$User.set(User);
});
});
angular.module('Test').controller('userFromIdCtrl', function($User, $http) {
$http.get('/getUserFromId/test')
.success(function(User) {
$User.set(User);
});
});
angular.module('Test').controller('userCtrl', function($scope, $User) {
$scope.User = $User;
});
This way is not good because the userCtrl is called before the $http callback (from the router's controllers), so the User is actually empty into the page (i was hopping it will be automatically updated).
Before i try to manage with it (using $rootScope.$apply()), i am wondering what is the more optimize way to do this kind of process (loading datas from router's controller then display it).
Do you use as many controllers as i do ? Do you process these REST APIs calls in the same controller that "bind" your view ? I am interesting to know !
When you define your routes you can define an additional value named resolve which is an object where each field is a promise that when resolved will be injected into your controller:
Route Definition:
when('/user/:pseudo', {
templateUrl: 'views/user.html',
controller: 'userFromPseudoCtrl'
resolve: {dataNeeded: userPseudoService.getUserData()});
Service (new):
angular.module('Test').service('userPseudoService', function($http){
return $http.get('/getUserFromPseudo/test');
});
Controller:
angular.module('Test').controller('userFromPseudoCtrl', function(dataNeeded){});
The route will not change until the promise is resolved.

Resources