I'm using UI-Router for AngularJS and here is the question - at the moment when I'm clicking a link which sends me to specific state (using ui-sref) I want to send AJAX request to back end, get data, and render them in the template related to this new state. To which event should I listen for making AJAX request? Could you please give me a code example for this listener? I understand that the question seems to be simple, but I'm new in AngularJS world.
Thank you.
Have a look at the resolve property on your state configuration. See ui-router wiki
Then inject the resolved property as a dependency of the controller.
Example:
$stateProvider.state('about', {
templateUrl: 'about.html',
controller: 'AboutController',
resolve: {
something: function ($http) {
// make ajax request
return $http.get(...).then(function (response) {
return response.data;
});
}
}
})
In the controller, we inject the data that will be resolved, i.e. something :
app.controller('AboutController', function (..., something, ...) {
// The data resolved by ui-router is ready when the controller is instantiated
}
Related
As displayed below, even though i've 2 diff html i am using same controller so i want to have the scope shared between 2 html.
Is it possible to configure this in routeProvider?
.when('/cform', {
templateUrl: '/partials/clientForm.html',
controller: 'ClientCtrl', //Same Controller
})
.when('/vwClientManagement', {
templateUrl: '/partials/clientManagement.html',
controller: 'ClientCtrl' //Same Controller
})
$scope.showClientAddUpdateView = function(action,clientId) {
$scope.client.name="Hello World"; // This is getting displayed in UI.
$http.get("/qCli", { params:{id : clientId}}).then(
function successCallback(response){
//$scope.client = response.data;
$scope.client.name="Hello World After Ajax"; // This is not getting displayed in UI.
$location.path('/cform');
},function errorCallback(response){
console.log("err searching clients");
}
);
};
Update:
Scanario 1 - changing route and setting scope in ajax success call back, loosing only those value which i have set in success callback after route change
Scanario 2 - updating scope in ajax success callback but not changing route, view is updated with correct values.
Added More details More Details
In my opinion, it's better to use Service, for sharing data between Angular controllers. Generally, angularjs is very flexible, and there is at least few ways to solve your problem: using services, using $state.go services, using stateparams, using rootscope...
Check this issue, there is a bunch of related and useful advices: Share data between AngularJS controllers
While using this:
.when('/cform', {
templateUrl: '/partials/clientForm.html',
controller: 'ClientCtrl', //Same Controller
}).when('/vwClientManagement', {
templateUrl: '/partials/clientManagement.html',
controller: 'ClientCtrl' //Same Controller
})
You are not using one unique controller. You are creating, for each view, an instance of ClientCtrl.
So they don't share there scope, each have a different and independant scope.
It seems you are trying to avoid using services whereas this would be best done in this case. There are severals (1, 2) answers on Stack Overflow dealing with this subject.
I've a page with Navbar and Sidebar that remains common across most of the pages and hence I load all data related to LoggedInUser in NavbarController.
This data ($scope.loggedInUser) is used in other Controllers (child controller) as well. Since call to get User data is async, many times, child controller tries to access data before it is returned from the server.
What is the best way to ensure, all promises of parent controller are resolved before child controller starts it's work?
You should use resolve from $stateProvider. This function ensures that all the promises are resolved before loading a new state.
A resolve is a property you can attach to a route in both ngRoute and the more robust UI router. A resolve contains one or more promises that must resolve successfully before the route will change. This means you can wait for data to become available before showing a view, and simplify the initialization of the model inside a controller because the initial data is given to the controller instead of the controller needing to go out and fetch the data.
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
in controller
app.controller("newsController", function (message) {
$scope.message = message;
});
Hope this will help you
Given the following state in ui-router:
.state('some.state', {
url: '/some/:viewType',
templateUrl: 'myTemplate.html',
controller: 'SomeStateController',
controllerAs: 'vm',
data: {
authorizedFor: [SOME_ROLE]
}
}
I'm trying to use the "data" object for a state to help control access to authorized states. Separately, I handle the $stateChangeStart event to look at data.authorizedFor and act accordingly.
The problem, though, is that the list of authorized roles might change based on the value of :viewType. I thought I could let data:{} be a function, inject $stateParams, and handle the logic there...but that won't do.
So, I tried using the params object instead, but at the $stateChangeStart time, the :viewType is not yet accessible from $state.params or $stateParams.
Stepping through in dev tools, I noticed that $state.transitionTo.arguments is populated, but it seems awfully hacky to go that route.
params: {
authorizedFor: function($state) {
console.log($state.transitionTo.arguments[1].viewType); // has value I need
}
}
Any suggestions?
My suggestion is to use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.
for example:
$stateProvider
.state('profile', {
url: '/profile',
templateUrl: 'profile.html',
resolve:{
'ProfileService': function(ProfileService){
return ProfileService.promise_skillRecommendation_mock;
}
}
})
The profileService code:
var app = angular.module('app').service("ProfileService", function($http){
var myData = null;
var promise_skillRecommendation_mock =
$http.get('Mock/skillRecommendation-mock.json')
.success(function(data){
myData = data;
});
return{
promise_skillRecommendation_mock: promise_skillRecommendation_mock,
get_skillRecommendation: function(){
return myData;
}
};
});
and the controller code which will use this service is:
angular.module('app').controller('ProfileController', function($scope, $http, ProfileService){
$scope.skillRecommendation = ProfileService.get_skillRecommendation();
The object in resolve below must be resolved (via deferred.resolve() if they are a promise) before the controller is instantiated. Notice how each resolve object is injected as a parameter into the controller.
by using this code, the page will be displayed only after that the promise will be resolved.
for more info please view this page: https://github.com/angular-ui/ui-router/wiki
Probably it's just as easy as I think it is, but I cannot really find an answer to my question on the internet, so I hope you guys know the answer just by looking at a small piece of my code.
Problem: I'm using the UI router in Angular and it loads the template before all the data is loaded. So all input fields receive the correct values AFTER the template is already loaded. So the input fields are empty for a second or two....
I think my resolve is not as it should be:
So my ui-router code looks something like this (check the resolve object):
$stateProvider.state('teststate', {
url: '/test/',
templateUrl: 'app/page/template.html',
controller: 'testCtrl',
resolve: {
access: ["Access", function(Access) { return Access.isAuthenticated(); }],
UserProfile: 'UserProfile'
}
});
Now the controller contains the promise to get some data from an API url:
function TestCtrl($scope, $state, $stateParams, TestService) {
TestService.get($stateParams.id).then(function(response) {
$scope.data = response;
});
}
Now the service (which connects to the API) should return the promise to the Controller:
TestService.factory('TestService', ['Restangular', function(Restangular) {
var factory = {};
factory.get = function(id) {
return Restangular.one('api/test', id).get();
}
return factory;
}]);
Now, could the problem be, that because the TestService.get() (which connects to the API) within the Controller, gets executed NOT before the template is loaded, because it's not inside the resolve object? So the UI router doesn't resolve the call to the API? I'm just curious or I should move all methods which make API calls, to the resolve object of each stat inside the $stateProvider.
I could run a lot of tests, but if someone just directly knows the answer by just looking at this question, it helps me a lot.
Your assumptions are all correct.
If you resolve the TestService.get in routing config the data would be readily available to controller as an injectable resource
If you don't want your controller to run and your template to show before all your API calls are finished, you have to put all of them inside ui-routers resolve.
However, if API requests can take a little while it seems better UX to transition to the new page immediately and show some kind of loading indicator (e.g. block-ui) while your API call is running.
I've got a ui-router application with a large number of states, and I need to be able to pass a model through $stateParams. On every $http request, I check the response for a "STATE" parameter, which is returned from the server. If it exists, I execute $state.go(STATE).
So effectively, I've got my $stateProvider:
$stateProvider
.state('Account', {url: '/Account', template: '<ui-view/>'})
.state('Account.name', {
url: '/Name',
templateUrl: 'app/Account/partials/Name.html',
controller: 'AccountNameController as nameVm'
})
And many more states that look just like this.
I have a data model that is just a factory with an object that is get and set via functions. So whenever I call saveAccount(), it takes the model and sends it to a Web API backend. The backend verifies the data and sends it back with a STATE parameter (either account.invalid, account.valid, or account.needsMoreInfo). Here's my $httpInterceptor
.factory('httpInterceptor', ['$q', '$injector',
function ($q,$injector) {
return {
'response': function(response) {
if(response.data.state){
$injector.get('$state').go(response.data.state, response.data.account);
}
return response;
}
};
}
])
As you can see, I'm trying to send the account through the stateparams.
In the controller, I basically need to be able to say vm.account = $stateParams.account
My question is:
How can I modify my $states to both have a named controller and also take a state parameter and access that from the controller?
The reason I'm not passing the data through a service is because there are multiple models, so I can't just provide the name of the service in the $httpInterceptor because it isn't constant.
EDIT: Figured it out
Here's what my controller needed to have in it:
if ($stateParams && $stateParams.data){
vm.Account = $stateParams.data;
}
And here's what the state ended up looking like:
.state('taxAccount.invalid', {
url: '/Invalid?params',
templateUrl: 'app/TaxAccount/partials/Invalid.html',
controller: 'taxAccountInvalidController as invalidVm',
params:{data:null}
})
I didn't realize I could put params:{data:null}. I thought the stateParams had to go in the controller declaration in the state configuration.
Here's what my controller needed to have in it:
if ($stateParams && $stateParams.data){
vm.Account = $stateParams.data;
}
And here's what the state ended up looking like:
.state('taxAccount.invalid', {
url: '/Invalid?params',
templateUrl: 'app/TaxAccount/partials/Invalid.html',
controller: 'taxAccountInvalidController as invalidVm',
params:{data:null}
})
I didn't realize I could put params:{data:null}. I thought the stateParams had to go in the controller declaration in the state configuration.