I want to fetch some data from a server and copy it to a service before a routechange is completed:
when(
'/detail/:id',
{
templateUrl: './partials/views/detail.php',
controller: 'detailCtrl',
resolve: {
init: function($route,$q,shipmentn){
var deffered = $q.defer();
shipmentn.getSingle($route.current.params.id).then(function(promise){
shipmentn.data = promise.data;
deffered.resolve(promise);
});
return deffered.promise;
}
}
}
)
As you can see, i am doing this inside the then() function to make sure that the request has been completed.
What bothers me, is that i have to inject another dependency (init) into my controller and have to return a promise which is never used.
How could i avoid this?
A trick might be to pass the service you want in your controller to the resolve function of the defered object, after the service itself finished its loading:
when('/detail/:id', {
templateUrl: './partials/views/detail.php',
controller: function($scope,service){}, //<-- Controller with the service dependency
resolve: {
service: function($route,$q,shipmentn){
var deffered = $q.defer();
shipmentn.getSingle($route.current.params.id).then(function(promise){
shipmentn.data = promise.data;
deffered.resolve(shipmentn); //<-- the deffered resolves the service
});
return deffered.promise;
}
}})
regards
If you are only looking to send data to the controller, you don't need to store it in the service, it can just be resolved to the controller:
router:
when('/detail/:id', {templateUrl: './partials/views/detail.php', controller: 'detailCtrl',resolve: {
shipment: function($route,$q,shipmentn){
return shipmentn.getSingle($route.current.params.id));
}
}})
service:
getSingle: function(id){
var deferred = q.defer();
$http.get(...).then(function(response){
deferred.resolve(response.data);
}
return deferred.promise;
}
controller:
module.controller('detailController', ['shipment', function(shipment){
...
}]);
On the controller, shipment will be whatever is returned from response.data in the http.get.
Related
I'm using ui-router in my app.
app.config(['$stateProvider', function($stateProvider){
$stateProvide.state('State1', {
url:'/State1',
resolve: {
data: function(Service){
return Service.init();
}
},
views: {
"header": {
templateUrl: 'views/header.tpl.html'
},
"center":{
templateUrl: 'views/center.tpl.html'
},
"footer": {
templateUrl: 'views/footer.tpl.html'
}
}
}
})
}
]);
I tried to load a json file from the server and resolve it in the router.
In the resolve object, I call to my service that is responsible for returning promise.
Service.js:
app.service("Service", ['$rootscope', '$http', function($rootscope, $http){
var promise;
this.init = function(){
promise = this.loadData();
return promise;
};
this.loadData = function(){
var url = "users/getData/json.json";
return $http.get(url).then(function(response){
return response.data;
}, function(error){
alert(error);
})
};
}])
center.tpl.html:
<aside id="first-item" ng-controller="FirstController as firstController">
<first-directive>
</aside>
<aside id="second-item" ng-controller="SecondController as secondController">
<second-directive>
</aside>
This is the controller to which I would like to get the resolved data.
FirstController.js:
app.controller('FirstController', ['$scope', 'data', function($scope, data){
this.myData = data;
}]);
I got the next error: Unknown provider: dataProvider < - data. Why?
since promise is never resolved before returning. try this one.
app.service("Service", ['$rootscope', '$http', function($rootscope, $http){
var promise;
this.init = function(){
return this.loadData();
};
this.loadData = function(){
var url = "users/getData/json.json";
return $http.get(url).then(function(response){
return response.data;
}, function(error){
alert(error);
})
};
}])
From the ui-router docs:
// The controller waits for every one of the above items to be
// completely resolved before instantiation. For example, the
// controller will not instantiate until promiseObj's promise has
// been resolved. Then those objects are injected into the controller
// and available for use.
It is cleared that the resolved variables will only be available in the controllers defined in the state config. You can not resolve those variables in a normal controller and you are trying to use data from your state config in your controller and hence you are getting that error.
But, to get those data in your FirstController, you can do like this:
app.config('$stateProvider', ['$rootScope', function ($stateProvider) {
$stateProvide.state('State1', {
url: '/State1',
resolve: {
data: function (Service) {
var data = Service.init();
$rootScope.$broadcast("dataReceivedFoo", {data: data});
return data;
}
},
views: {
"header": {
templateUrl: 'views/header.tpl.html'
},
"center": {
templateUrl: 'views/center.tpl.html'
},
"footer": {
templateUrl: 'views/footer.tpl.html'
}
}
})
}]);
And, then read in your controller:
app.controller('FirstController', ['$scope' function($scope){
$scope.$on("dataReceivedFoo", function(response) {
$scope.myData = response.data;
})
}]);
Basically, we are broadcasting the data from your state configuration and then receiving in your FirstController.
I think adding controller: 'FirstController' line to state will solve the problem.
app.config(['$stateProvider', function($stateProvider){
$stateProvide.state('State1', {
url:'/State1',
controller: 'FirstController',
resolve: {
data: function(Service){
return Service.init();
}
},
views: {
"header": {
templateUrl: 'views/header.tpl.html'
},
"center":{
templateUrl: 'views/center.tpl.html'
},
"footer": {
templateUrl: 'views/footer.tpl.html'
}
}
}
})
}
]);
Resolve
You can 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.
The resolve property is a map object. The map object contains
key/value pairs of:
key – {string}: a name of a dependency to be injected into the
controller.
factory - {string|function}: If string, then it is an
alias for a service. Otherwise if function, then it is injected and
the return value is treated as the dependency. If the result is a
promise, it is resolved before the controller is instantiated and its
value is injected into the controller.
https://github.com/angular-ui/ui-router/wiki
I have tried everything to get ui-router's resolve to pass it's value to the given controller–AppCtrl. I am using dependency injection with $inject, and that seems to cause the issues. What am I missing?
Routing
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: 'AppCtrl',
controllerAs: 'vm',
resolve: {
auser: ['User', function(User) {
return User.getUser().then(function(user) {
return user;
});
}],
}
});
Controller
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope'];
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
console.log(auser); // undefined
...
}
Edit
Here's a plunk http://plnkr.co/edit/PoCiEnh64hR4XM24aH33?p=preview
When you use route resolve argument as dependency injection in the controller bound to the route, you cannot use that controller with ng-controller directive because the service provider with the name aname does not exist. It is a dynamic dependency that is injected by the router when it instantiates the controller to be bound in its respective partial view.
Also remember to return $timeout in your example, because it returns a promise otherwise your argument will get resolved with no value, same is the case if you are using $http or another service that returns a promise.
i.e
resolve: {
auser: ['$timeout', function($timeout) {
return $timeout(function() {
return {name:'me'}
}, 1000);
}],
In the controller inject the resolve dependency.
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope','auser']; //Inject auser here
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
vm.user = auser;
}
in the view instead of ng-controller, use ui-view directive:
<div ui-view></div>
Demo
Here is how I work with resolve. It should receive promise. So I create service accordingly.
app.factory('User', function($http){
var user = {};
return {
resolve: function() {
return $http.get('api/user/1').success(function(data){
user = data;
});
},
get: function() {
return user;
}
}
});
This is main idea. You can also do something like this with $q
app.factory('User', function($q, $http){
var user = {};
var defer = $q.defer();
$http.get('api/user/1').success(function(data){
user = data;
defer.resolve();
}).error(function(){
defer.reject();
});
return {
resolve: function() {
return defer.promise;
},
get: function() {
return user;
}
}
});
These are almost identical in action. The difference is that in first case, service will start fetching date when you call resolve() method of service and in second example it will start fetch when factory object is created.
Now in your state.
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: function ($scope, $rootScope, User) {
$scope.user = User.get();
console.log($scope.user);
},
controllerAs: 'vm',
resolve: {
auser: function(User) {
return User.resolve()
}
}
});
I need to do a request inside the RUN method to retrieve de user data from an api.
The first page (home), depends on the user data.
This is the sequence of dispatchs in my console:
CONFIG
RUN
INIT GET USER DATA
SIDEBAR
HOME
SUCCESS GET USER DATA
My problem is, i need to wait user data before call sidebar and home (controller and view) and i don't know how can i do this.
UPDATE
I have this until now:
MY CONFIG:
extranet.config(['$httpProvider', '$routeProvider', function ($httpProvider, $routeProvider) {
// My ROUTE CONFIG
console.log('CONFIG');
}]);
My RUN:
extranet.run(function($rootScope, $location, $http, Cookie, Auth, Session) {
console.log('RUN');
var token = Cookie.get('token');
// The login is done
var success = function (data) {
Session.create(data);
console.log('USER DATA SUCCESS');
};
var error = function () {
$location.path('/login');
};
// GET USER DATA
Auth.isAuthenticated().success(success).error(error);
});
MY CONTROLLER MAIN:
extranet.controller('MainCtrl', function ($scope, $location) {
console.log('MAIN CONTROLLER');
});
By using resolver
extranet.config(['$httpProvider', '$routeProvider', function ($httpProvider, $routeProvider) {
// My ROUTE CONFIG
$routeProvider.when('/', {
templateUrl: "/app/templates/sidebar.html",
controller: "siderbarController",
title: "EventList",
resolve: {
events: function ($q, Cookie,Session) {
var deffered = $q.defer();
Cookie.get('token').$promise
.then(function (events) {
Session.create(data);
console.log('USER DATA SUCCESS');
deffered.resolve(events);
}, function (status) {
deffered.reject(status);
});
return deffered.promise;
}
}
}]);
I hope you get some idea.
If you are using AngularJS methods for server requests you will get a promise. A promise gets resolved as soon as the response is recieved. All defined callbacks "wait" until the resolve.
Naive solution
So, you will use $http or even $resource if you have a REST-like backend:
var promise = $http.get(userDataUrl, params)
$rootScope.userDataPromise = promise;
After that you can use that promise whereever you need the data:
$rootScope.userDataPromise.then(myCallback)
Better solution
Using $rootScope for that purpose is not an elegant solution though. You should encapsulate the Userdata stuff in a service and inject it whereever you need it.
app.factory('UserData', ['$http',
function($http) {
var fetch = function() {
return $http.get(userDataUrl, params)
};
return {
fetch: fetch
};
}
]);
Now you can use that service in other modules:
app.controller('MainCtrl', ['$scope', 'UserService',
function ($scope, UserService) {
var update = function(response) {
$scope.userData = response.userData;
}
var promise = UserService.fetch();
promise.then(update)
}
);
I want to load some configuration to each controller in app.config section. Each controller needs a different, but non-mutually-exclusive set of data to be loaded. I can't figure out how to achieve this.
.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider){
$routeProvider
.when('/', {
templateUrl: "partials/pages/dashboard.html",
controller: "dashboard_controller",
resolve: { dash_config: 'SomeConfigD'},
})
.when('/a', {
templateUrl: "partials/pages/a.html",
controller: "a_controller",
resolve: { dash_config: 'SomeConfigA'},
})
}])
However, I don't want to write seperate factories for someConfigA and someConfigD, since they share code. I want something like,
app.factory('configFactory', function(...){
var factory = ;
function get1(){
// some $http calls here and return a promise
}
function get2(){
// some $http calls here and return a promise
}
function get3(){
// some $http calls here and return a promise
}
factory.configA = function(){
// return a promise to resolve both get1 and get2
};
factory.configD = function(){
// return a promise to resolve both get2 and get3
};
})
How can I do this?
It sounds like what you are looking for is $q.all, which you can read about here.
I also made a fiddle that uses your factory, although I do it in the run method of the module since I didn't want to deal with creating the factory. It looks something like this:
f.configA = function(){
var get12 = $q.all([
get1().then(thenFn),
get2().then(thenFn)
]).then(function() {
console.log('both resolved');
});
return get12;
};
the function in the then is only called when both promises have been resolved (which happen at different times, simulated with $timeout
Now the get functions can be reused, Hope this helped!
I am building an angular app using angular-ui-router. The backend has a REST api that gives me the url to a form based on a ticket id. In app.js, I want to dynamically set the template based on a query to this REST service. Example:
$stateProvider
.state('form', {
url: '/form/:id',
templateProvider: function ($resource, formResolver, $stateParams) {
//formResolver calls the REST API with the form id and gets back a URL.
return formResolver.resolve($stateParams.id).then(function(url) {
return $resource(url).get();
};
},
controller: 'MyCtrl'
});
The problem is that I end up returning a promise and templateProvider requires a string of content. What I would like to do is just return the url:
$stateProvider
.state('form', {
url: '/form/:id',
//I would like to inject formResolver, but I can't
templateUrl: function (stateParams, formResolver) {
return formResolver.resolve(stateParams.id);
},
controller: 'MyCtrl'
});
But I don't get dependency injection when using templateUrl instead of templateProvider as per https://github.com/angular-ui/ui-router/wiki#wiki-templates, and I still have the problem of it returning a promise. I am thinking maybe my only solution is not to use the promise api.
Turns out there was something wrong with the way I was using $resource. I'm still not sure what. From looking at the source for angular-ui-router, the function can return a promise. I ended up copying some of the code from https://github.com/angular-ui/ui-router/blob/master/src/templateFactory.js to get the following, which works:
templateProvider: function ($http, formService, $stateParams) {
return formService.getFormUrl($stateParams.id).then(function(url) {
return $http.get(url);
}).then(function(response) {
return response.data;
})
Function for templateProvider may return promise from $resource but in the end it has to return string.
templateProvider: function (SomeService) {
var promise = SomeService.get().$promise;
promise.then(function (data) {
console.log(data) // it has to be string!
});
return promise;
}
If there is an object one of the solutions would be to make another promise.
templateProvider: function (SomeService, $q) {
var defer = $q.defer();
var promise = SomeService.get().$promise;
promise.then(function (data) {
console.log(data) // let say this is {html: '<p>some template</p>'}
defer.resolve(data.html);
});
return defer.promise;
}
SomeService returns $resource.