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!
Related
Lets say I have a an angular ui router route set up. When I change to that state, I'm telling Angular that I want it to resolve a factory call first then load the view. But what happens when that api call is empty? I would like to inform the user that there was no results found and stay on at my original state. Not transition to another view with no data to display. What is the best way to achieve this?
The route (which works as expected so far when I know there will be a return)
'use strict';
angular.module('testApp')
.config(function ($stateProvider) {
$stateProvider
.state('spinnerTest', {
url: '/spinner_test',
templateUrl: 'app/spinnerTest/spinnerTest.html',
controller: 'SpinnerTestCtrl',
resolve: {
names: function(NamesService){
//What happens if I return an empty array []?
//How do I return to the previous state?
NamesService.getNames();
}
}
});
});
You can simply reject promise in resolve in case of empty array:
resolve: {
names: function(NamesService) {
return NamesService.getNames().then(function(names) {
return names.length == 0 ? $q.reject('no names') : names;
});
}
}
This is a cross cutting concern, it is probably not unique to the Name service, but other services you are using as well.
Since you didn't post the code to the Name service (NameService service is redundant) I will assume it uses either the $http or $resource service. You can then use a $httpInterceptor that will trigger the display of a message to the user that "The selection is unavailable at this time".
You could call $state.go in your resolve, if you'd like
'use strict';
angular.module('testApp')
.config(function ($stateProvider) {
$stateProvider
.state('spinnerTest', {
url: '/spinner_test',
templateUrl: 'app/spinnerTest/spinnerTest.html',
controller: 'SpinnerTestCtrl',
resolve: {
names: function(NamesService, $state){
//What happens if I return an empty array []?
//How do I return to the previous state?
return NamesService.getNames().then(function(names){
if (!names.length) {
return $state.go('otherState');
}
return names;
});
}
}
});
});
I'm just starting out in angular, and i'm building a simple item management app that loads items from a json file, and shows the items in a list view. I'll be allowing the user to edit and create new data as well, but i can't seem to get past the first step.
When I load the data directly in my list controller, It works just fine. While reading up on best practices, it seems like you shouldn't communicate directly with a json file in your controller, but rather handle these things within a factory (let me know if i'm mistaken). I can't get it to work though. Here's my code:
var app = angular.module('itemsApp', ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider
.when('/', {
controller:'ListCtrl',
templateUrl:'list.html'
})
.when('/edit/:itemId', {
controller:'EditCtrl',
templateUrl:'detail.html'
})
.when('/new', {
controller:'CreateCtrl',
templateUrl:'detail.html'
})
.otherwise({
redirectTo:'/'
});
})
app.factory('ItemsFactory',function($http){
return {
getItems: function() {
return $http.get('js/items.json')
.then(function(res){
return res.data;
});
}
};
});
app.controller('ListCtrl', function($scope, $http, ItemsFactory) {
$http.get('js/items.json')
.then(function(res){
$scope.items = res.data;
});
});
The controller works fine as i have it here, however, when i try to just set $scope.items to the result of ItemsFactory.getItems();, I get nothing. Any ideas why?
Returning inside the then promise method doesn't return any to the caller of getItems (like in any other callback). I suggest you to manage that kind of situation in this way:
app.factory('ItemsFactory',function($http){
return {
getItems: function() {
return $http.get('js/items.json');
}
};
});
app.controller('ListCtrl', function($scope, ItemsFactory) {
ItemsFactory.getItems().then(function(res){
$scope.items = res.data;
});
});
Hope it helps.
Dario
The best practices you are reading are absolutely right and you should have all the server communications inside one seperate module which will just have all the factories doing server exchange there.
Then you can just inject this, let's say integration module and all the factories required for server communctions become available.
Now, a sample factory can be like:
angular.module('integrationModule')
.factory('ItemsFactory',function($http){
return {
getItems: function() {
return $http.get('js/items.json');
}
};
});
app.controller('ListCtrl', function($scope, ItemsFactory) {
ItemsFactory.getItems().then(function(res){
$scope.items = res.data;
});
});
//credits Dario
or this is my preferred way(no promises needed here)
angular.module('integrationModule')
.factory('getLocaleProperties', ['$http', function($http) {
return function(requestObj, callBackFunc){
console.log('#getLocaleProperties:');
console.log(requestObj);
$http.get('assets/locale/localeProperties.json')
.success(function(data) {
callBackFunc(data);
})
.error(function(){
console.log('error in get Locale properties');
});
}
}])
Now if you notice, I am passing a callback function which is executed only when the $http call is successful, so in controllers you can inject this factory and pass it some function like:
getLocaleProperties({//some request object when posting/as query params in url}, function(data){
//do some operation on the data
})
This way I can call the factory from different controllers and perform different actions when the call is successful. Just I need to pass different callback functions.
You can also use promise, like the way you are doing, but they are only needed when you want a synchronous call like behaviour, rest can be done be callbacks.($http call is specified async in angular source code).
Hope this helps.
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.
I'm having trouble figuring out how to get the routeProvider to wait until a remote call returns. The best solution I've seen for far was the example here: delaying angular route change
. Unfortunately, when I tired to apply that example to my own code, the binding would trigger before the data was actually loaded. Does anyone know of another example that uses the new Resource syntax from angular 1.1.5 ($promise can be accessed directly )?
Here is what my code looks like:
var productModule = angular.module('productModule', ['ngResource', 'ngLocale']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {templateUrl: 'partials/partial1.html',
controller: 'ResourceCtrl as appController' ,
resolve:
{
productData: function($resource)
{
console.log(["calling service"]);
return $resource(Services.ProductServices.PATH).query(null,
function(data){
console.log(["call succeeded"]);
},
function(data){
console.log(["calling failed"]);
}).$promise;
}
}
});
$routeProvider.when('/view2', {templateUrl: 'partials/partial2.html'});
$routeProvider.otherwise({redirectTo: '/view1'});
}]) ;
productModule.controller('ResourceCtrl','$scope','productData',function($scope,productData) {
$scope.productData = productData;
console.log(["promise resolved"]);
}]);
If I run that code, the console would display:
calling service
promise resolved
call succeeded
It should be as simple as this:
resolve: {
productData: function(ProductServices) {
return ProductServices.query().$promise.then(function(data){
return data;
});
}
}
If your Service looks something like this:
myApp.factory('ProductServices', function($resource) {
return $resource('/path/to/resource/:id', { id: '#id' });
});
This subject has been already asked but I couldn't figure out what to do in my case.
Using AngularJS 1.0.5:
Before showing the view "login", I want to get some data and delay the view rendering while the data isn't loaded from an AJAX request.
Here is the main code. Is it the good way?
angular.module('tfc', ['tfc.config', 'tfc.services', 'tfc.controllers']).config([
'$routeProvider', '$locationProvider', '$httpProvider',
function($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/login', {
templateUrl: 'views/login.html',
controller: "RouteController",
resolve: {
data: function(DataResolver) {
return DataResolver();
}
}
});
}
]);
module_services = angular.module("tfc.services", []);
module_services.factory("DataResolver", [
"$route", function($route) {
console.log("init");
return function() {
// Tabletop is a lib to get data from google spreadsheets
// basically this is an ajax request
return Tabletop.init({
key: "xxxxx",
callback: function(data, tabletop) {
console.log("[Debug][DataResolver] Data received!");
return data;
}
});
};
}
]);
The point of AngularJS is that you can load up the templates and everything and then wait for the data to load, it's meant to be asynchronous.
Your view should be using ng-hide, ng-show to check the scope of the controller so that when the data in the scope is updated, the view will display. You can also display a spinner so that the user doesn't feel like the website has crashed.
Answering the question, the way you are loading data explicitly before the view is rendered seems right. Remember that it may not give the best experience as there will be some time to resolve that, maybe giving an impression that your app stopped for some moments.
See an example from John Pappa's blog to load some data before the route is resolved using angular's default router:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
You basically use the resolve parameters on the route, so that routeProvider waits for all promises to be resolved before instantiating the controller. See the docs for extra info.