New to AngularJS and I guess I don't understand how to call one Promise method from another with the same factory. Every time my code gets to the $http.get within processPerson, I get a Function Expected error in IE, or an Object is not a Function error in Chrome. I've tried reorganizing code many times, multiple factories, etc, and generally get the same error. The only time I can get this to work is if I combine the functions where the processPerson function is embedded within the success of the getPersonnel.
Code:
(function(){
var app = angular.module('hrSite', ['personnel']);
app.controller('PersonnelController', function($scope, personnelFactory){
var personnelPromise = personnelFactory.getPersonnel();
personnelPromise.then(function(personnel){
var perDefs = new Array();
$.each(personnel.data.value, function( i, person ){
var perDef = personnelFactory.processPerson(person);
perDefs.push(perDef);
});
$q.all(perDefs).then(function(){
$scope.personnel = personnel.data.value;
});
});
});
})();
(function(){
var personnelModule = angular.module('personnel', []);
personnelModule.factory('personnelFactory', function($http, $q) {
var getPersonnel = function(){
return $http.get("/sites/Development/_api/web/lists/getbytitle('Personnel')/items");
};
var processPerson = function(person){
var deferred = $q.defer();
$http.get("/sites/Development/_api/web/lists/getbytitle('Personnel Skills')/items?$select=*,Skill/Id,Skill/Title&$filter=PersonId eq '"+person.Id+"'&$expand=Skill").then(function(skills){
person.Skills = skills.data.value;
person.SkillsId = [];
$.each(skills.data.value, function( j, skill ){
person.SkillsId.push(skill.Id);
});
deferred.resolve();
});
return deferred.promise();
};
return {getPersonnel: getPersonnel,
processPerson: processPerson}
});
})();
Nevermind - I figured it out. I was migrating code from a jQuery project and in jQuery, you return a promise like this:
return deferred.promise();
Since Angular has its own deferred feature, $q, I began using that, without realizing that the notation to return a promise was slightly different:
return deferred.promise;
No () in that, which was really screwing things up. Now everything seems to be working fine.
Related
I need to use angular service to create cascading drop-downs.The commented code I created for testing purpose and it is working fine. I need to create two services to call two methods from the MVC controller : GetCompanies() and GetDocTypes()
My questions are: Is my first service correct and how can I call the services from the controller?
Thank you.
/// <reference path="angular.js" />
//var myApp = angular
// .module("myApp", [])
// .controller("companyController", function ($scope, $http) {
// $http.post('CurrentSettings/GetCompanies')
// .then(function (response) {
// var response = $.parseJSON(response.data);
// $scope.currentSettings = response;
// });
// });
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', function () {
$http.post('CurrentSettings/GetCompanies')
.then(function (response) {
var response = $.parseJSON(response.data);
$scope.currentSettings = response;
});
});
myApp.controller("companyController", function ($scope, getCompanies, $http) {
});
The problem with your service is two-fold:
Firstly, there is no way to call the service. You injected it fine, but now what? Think of your service as an API; it's no good just having a reference to it somewhere, you need to be able to use it. I would change to the following:
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', ["$http", function($http) {
this.currentSettings = "Hello";
$http.post('CurrentSettings/GetCompanies')
.then(function(response) {
var response = $.parseJSON(response.data);
this.currentSettings = response;
});
}]);
myApp.controller("companyController", ["$scope", "getCompanies",
function($scope, getCompanies) {
$scope.currentSettings = getCompanies.currentSettings;
}]);
Note a few things:
You need to explicitly inject $http into your service.
I specify the names of the services that I'm injecting as part of an array that includes the function. This actually allows you to name the parameters anything you want, and is considered a best practice.
The service doesn't use $scope directly. Instead, it makes a field available to clients of the service. That client (the controller in this case) can then do with the value whatever it wants, including assigning it to a $scope field.
The controller reads this field from the service. It could also call any functions you specified - making the service an API, as I mentioned before.
The second problem is one of timing. Notice that I used the super-original value of "Hello" to initialize the service field.
The value you receive from the service will depend on whether or not the controller reads the value after your call to the MVC controller returns.
To fix this, the service could expose a second field to indicate that the company list is fully loaded, but that really shifts the problem around instead of fixing it.
What you need is a function that returns a promise. If the value has already been loaded, the promise resolves immediately. If not, it returns a promise that will return once the $http call is done.
Here is the modified code:
var myApp = angular.module("myApp", []);
myApp.service('companiesService', ['$http', '$q', function($http, $q) {
var currentSettings = null;
this.getList = function() {
var def = $q.defer()
if (currentSettings) {
def.resolve(currentSettings);
} else {
$http.get('CurrentSettings/GetCompanies')
.then(function(response) {
var response = response.data;
currentSettings = response;
def.resolve(currentSettings);
});
}
return def.promise;
}
}]);
myApp.controller('companyController', ['$scope', 'companiesService',
function($scope, companiesService) {
$scope.currentSettings = '';
companiesService.getList().then(function(value) {
$scope.currentSettings = value;
});
}
]);
It becomes a bit more complicated because you have to use promises, but these are the things to note:
I changed the name of the service to make it more generic. It can now offer a number of company-related features.
currentSettings is no longer added to this on the service, but instead becomes a normal (private) variable. The calling code can only read it by calling the getList function.
getList returns a promise. The promise is resolved immediately if currentSettings has been assigned. If not, it only resolves once the value is received from the web service.
The controller calls getList and assigns the value to the $scope field in the then function.
I have been working with wrapping my head around the "angularjs" way of thinking (Angular 1) and I have a relatively ok grasp as I work my way through a small personal project. I am at a bit of a roadblock, not because I cannot get it to work, but I would like to know what the proper way to set up the data in my application.
The basic situation is this:
I have 3 json files:
categories.json
products.json
vendors.json
These hold the data (which I will fetch from a database later but am simplifying for now).
I basically need to load the data from all three of these files so that I can form a variable holding all "Products" (which is a JS class I declared separately).
I started off by storing the data inside one controller (relevant code below):
myApp.controller('productListController', ['$scope', '$http', '$q', function ($scope, $http, $q) {
var promises = [];
promises.push(getCategories($http));
promises.push(getVendors($http));
promises.push(getProducts($http));
$q.all(promises).then(function (response) {
//categories = response[0];
//vendors = response[1];
//products = response[2];
$scope.products = createProductList(response);
$scope.vendors = response[1].data;
$scope.vendorChecked = getCheckedVendors($scope.vendors);
})
This worked fine but I realized that I need this data in other views, which led me to try to move this code into a service.
The problem I had when doing this is that I do not know of a way for the controller to know that the service is done fetching the data so that I can then save it in the ProductListController $scope.
I would need to have a way to for example:
myApp.service('ProductService', ['$http', '$q', function ($http, $q) {
self = this;
var promises = [];
promises.push(getCategories($http));
promises.push(getVendors($http));
promises.push(getProducts($http));
$q.all(promises).then(function (response) {
//These three I would like to update in ProductListController
//when it is done.
self.products = createProductList(response);
self.vendors = response[1].data;
self.vendorChecked = getCheckedVendors(self.vendors);
})
Is this the correct approach to take? If so, how can I let the controller know that the service is done fetching the data and save for example:
$scope.products = ProductService.products;
$scope.vendors = ProductService.vendors;
$scope.categories = ProductService.categories;
Is this even the correct approach? Another approach I thought of was to use a factory instead of a service. Then I had another problem because I had for example:
return {
getProducts: function() {
//http get request code in here
return promise
},
getVendors: function() {
//http get request code in here
return promise
},
getCategories: function() {
//http get request code in here
return promise
},
getAllData: function () {
//in here I want to use the three promises in the 3 functions above
//but I am not able to call them from here. If I was able to do that
//then I could call this method from ProductListController and get the
//data that way.
}
I am sorry if this is long but I wanted to describe the different things I tried. I know I can make it work but I want to learn the right way, or a couple of right ways.
It is better to always return promise:
var promises = [];
promises.push(getCategories($http));
promises.push(getVendors($http));
promises.push(getProducts($http));
return $q.all(promises)
If you also not satisfied that in each controller you should call createProductList,getCheckedVendors - consider putting this tranforms to $http transformResponce https://docs.angularjs.org/api/ng/service/$http.
Or you can create your own promise. (Using $q.defer https://docs.angularjs.org/api/ng/service/$q).
Using servie or factory actually doesnt matter. This is factory:
var factory = {};
factory.getProducts: function() {
return promise
}
factory.getCategories: function() {
return promise
}
factory.getVendors: function() {
return promise
}
factory.getAllData: function () {
var promises = [];
promises.push(factory.getProducts());
promises.push(factory.getCategories());
promises.push(factory.getVendors());
return $q.all(promises)
}
return factory;
And in controler you just have:
MyFactory.getAllData().then(...)
I'm running into a problem trying to setup a fairly simple call using a factory and controller in angularjs. I've been attempting to follow the style guides of John Papa and Todd Motto in setting this up.
First I'm using 2 modules
(function(){
'use strict';
angular.module('app',[
'app.core',
'app.property'
]);
})();
In 'app.core' I define the factory
(function(){
'use strict';
angular.module('app.core')
.factory('dataservice',dataservice);
dataservice.$inject = ['$http','$q'];
function dataservice($http,$q) {
var service = {
getListing: getListing
};
return service;
function getListing() {
var def = $q.defer;
$http.get("http://acme.com/property/1?format=json")
.success(function(data){
service.getListing = data;
def.resolve(data);
});
return def.promise;
}
}
})();
and in 'app.property' I defined the controller
(function(){
'use strict';
angular.module('app.property')
.controller('PropertyCtrl',PropertyCtrl);
PropertyCtrl.$inject = ['dataservice'];
function PropertyCtrl(dataservice) {
var vm = this;
vm.listings = [];
activate();
function activate() {
return getListing().then(function(){});
}
function getListing(){
return dataservice.getListing().then(function(data){
vm.listings = data;
console.log("data is");
console.log(data);
return vm.listings;
});
}
}
})();
the error I get in the console output is
Error: dataservice.getListing(...) is undefined except when I inspect dataservice in chrome I can see
Further on I receive
TypeError: Cannot read property 'then' of undefined
TypeError: def.resolve is not a function
Despite these errors the remote call returns json fine.
Hoping someone with angular chops has an idea on where I went wrong.
You're very close. It should be $q.defer() but you've $q.defer
function getListing() {
var def = $q.defer();
$http.get("http://acme.com/property/1?format=json")
.success(function(data){
service.getListing = data;
def.resolve(data);
});
return def.promise;
}
You have to actually create the modules you are building on:
Put this above your app.property module's objects:
angular.module('app.property', []);
Put this above your app.core module's objects:
angular.module('app.core', []);
You are basically attaching a factory and a controller to modules that don't exist. You are trying to inject modules that don't exist into your primary module.
Here is a plunker showing the issues you were having resolved. Your code has some other issues, but at least it's finding the modules now, which was your original problem.
It should also be noted that mohamedrias is also correct - you had an error in syntax by not putting () on your defer call.
I updated my plunker to include his correction as well.
I have a pretty standard app which will display news items from a remote JSON feed. So basically I have decided to poll the remote server and store the JSON in localStorage (to enable offline usage). For the moment, I have a manual page/view I must click on to update the localStorage , this works fine.
The problem is that after I use my temporary manual update page, I then go to the news page/view and it is not updated. To view the current JSON contents I must hit refresh (while still developing in the browser.)
I'm totally new to Angular and have tried to find solutions to this myself - $watch or reload: true seem to be suggested as fixes, but I cannot get them to work in my case.
Route
.state('tab.news', {
url: '/news',
reload: true,
views: {
'news-tab': {
templateUrl: 'templates/news_home.html',
controller: 'newsCtrl'
}
}
})
factory
angular.module('schoolApp.services', [])
.factory('newsService', function($q) {
var newsHeadlines =localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail status
var newsHeadlinesObj = JSON.parse(newsHeadlines);// convert to an object
console.log("factory newsService ran");
return {
findAll: function() {
var deferred = $q.defer();
deferred.resolve(newsHeadlinesObj);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var deferred = $q.defer();
var newsItem = newsHeadlinesObj[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
Controller
schoolApp.controller('newsCtrl', function($scope, newsService) {
console.log ( 'newsCtrl ran' );
newsService.findAll().then(function (newsHeadlinesObj) {
$scope.newsHeadlinesObj = newsHeadlinesObj;
}, function(error){
console.log(error)
});
})
Looking at my console, the first time I read the news, the factory then controller run, but if I go to pull more data down, then go hack to news, only the controller runs, unless I refresh, then both run again.
I do not need the news view to update 'live' while still on it (but if that can be easilly done all the better) - just to pick up new data when you go back to news after being elsewhere in the app.
Thank you.
Factories return singletons and only run once. The object newsService is cached by angular. The var declarations for newsHeadlines and newsHeadlinesObj will only ever run once; meaning your promise returning methods will always resolve the promise with the same data that was retrieved when your factory was first instantiated. You should put them in a function and call it from your find methods on the singleton object.
.factory('newsService', function($q) {
function getHeadlines() {
var newsHeadlines = localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail
return JSON.parse(newsHeadlines);// convert to an object
}
return {
findAll: function() {
var headlines = getHeadlines();
var deferred = $q.defer();
deferred.resolve(headlines);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var headlines = getHeadlines();
var deferred = $q.defer();
var newsItem = headlines[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
PS - I'm sure you know and are planning to do things differently later or something, but just in case you don't: Using promises here is pointless and you have no need for $q here. You could simply return the data instead of returning the promises.
I solved this withouut promises, I just used $rootScope in the factory and $scope.$on in the controller; when I change the factory, i use $rootScope.$broadcast to tell the controller that I change it.
.factory('dataFactory', ['$http', '$rootScope', function ($http, $rootScope) {
var dataFactory = {
stock: null,
getStock: getStock
}
function getStock() {
$http.get("/api/itemfarmacia/").then(function success(res) {
dataFactory.stock = res.data;
$rootScope.$broadcast('changingStock'); //Ones who listen this will see it
}, function error(err) {
console.log("Bad request");
})
}
return dataFactory;
}])
and in the controller
.controller('atencion', ["$scope", "$state", "dataFactory", function ($scope, $state, dataFactory) {
$scope.stock = dataFactory.stock; //At first is null
dataFactory.getStock(); //wherever you execute this, $scope.stock will change
$scope.$on('changingStock', function () {//Listening
$scope.stock = dataFactory.stock; //Updating $scope
})
}])
how do you bootstrap a controller that loaded asynchronously via require.js?
if I have something like that:
$routeProvider.when('/',
{
templateUrl:'view1.html',
controller:'ctrl',
resolve:{
load:function($q){
var dfrd = $q.defer();
require(['view1-script'],function(){
dfrd.resolve();
})
return dfrd.promise;
}
}
})
why angular still won't find the controller? I am resolving the route after it loads the script
check out this plunkr
try calling $controllerProvider.register to create your controller. I would also call $apply() on the $rootScope after resolving the deferred because without it, the view does not seem to appear:
load: function($q, $rootScope){
var dfrd = $q.defer();
require(['view1'],function(){
dfrd.resolve();
$rootScope.$apply();
})
return dfrd.promise;
}
http://plnkr.co/edit/fe2Q3BhxPYnPmeiOORHP
in addition, here is a good post: http://weblogs.asp.net/dwahlin/archive/2013/05/22/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs.aspx
It's been 3 years, but just in case anyone still interested, a few months ago I wrote a post about a similar technique to do it.
The most important part is that second parameter of the method $routeProvider.when(route, ctrl) method can handle promises, so you can simply emulate it:
function controllerFactory(ctrl) {
return {
then: function (done) {
var self = this;
require(['./controller/' + ctrl], function (ctrl) {
self.controller = ctrl;
self.resolve = ctrl.resolve;
self.templateUrl = ctrl.templateUrl;
done();
});
}
};
}
And you can end up writing your route definition like this:
$routeProvider.
when('/some/route', controllerFactory('some/route')).
when('/other/route', controllerFactory('other/route'))