AngularJS Controller and Factory - angularjs

I've spent quite alot of time going over AngularJS these past few days, it's starting to all click now :) but the one question i can't seem to answer is how i get my factory to return the data as JSON - not as a promise OR even if i should!
There are a few reasons i can't see the result, A) the promise is incomplete, B) I shouldn't be doing it this way and should actually just stick with the 'then()' in the controller. Ideally i want to write one line in the controller but i always get an undefined unless i follow the pattern in the example.
Am i going against the grain on this where i don't need to?
// Will go into application.js
(function () {
var app = angular.module("ngOrderApp", []);
}());
// Will go into orderFactory.js
(function () {
var order = function ($http) {
var getOrdersJson = function () {
return [{ OrderId: 101 }, { OrderId: 102 }, { OrderId: 103 }];
}
var getOrdershttp = function () {
return $http.get('api/order')
.success(function (result) {
return result.data;
});
}
return {
getOrdersJson: getOrdersJson,
getOrdershttp: getOrdershttp
};
}
var app = angular.module("ngOrderApp").factory("order", order);
}());
// Will go into orderController.js
(function () {
var app = angular.module("ngOrderApp").controller('OrderController', function ($scope, order) {
$scope.jsonorders = order.getOrdersJson();
order.getOrdershttp().then(function (result) {
$scope.httporders = result.data;
});
});
}());

The whole point of promises is that you can't get the result of an asynchronous operation immediately.
So yes, you should use then to get its eventual result. It's three lines instead of one, but that shouldn't really be a problem. Once you get used to using promises, I'm sure you won't see it as so much of a big deal.
If that really doesn't sit well with you, you could consider using a $resource instead of using $http directly. This essentially allows you to assign a value directly to the place you want it instead of using then, and the rest of its contents will be filled in (asynchronously) when the request ultimately completes. Bear in mind that this still will not allow you to immediately access the result value. That's just not possible when you're working with asynchrony.

Binding directly to a promise worked in previous version of angular, but they got rid of this feature (I don't exactly know why) so, yes, just stick with the 'then()' in the controller.

Well this depends on the data that you are fetching. You could do a pre-fetch in your factory and store the data into a private variable like so:
(function () {
var order = function ($http) {
var getOrdersJson = function () {
return [{ OrderId: 101 }, { OrderId: 102 }, { OrderId: 103 }];
}
var getOrdershttp = function () {
return $http.get('api/order')
.success(function (result) {
return result.data;
});
}
var orders = [];
getOrdershttp().then(function(res) {
orders = res.data;
});
function getCahcedOrders(){ return orders; };
return {
getOrdersJson: getOrdersJson,
getOrdershttp: getOrdershttp,
getChacedOrders: getChacedOrders
};
}
var app = angular.module("ngOrderApp").factory("order", order);
}());
And this way your controller will only contain:
$scope.orders = order.getCachedOrders();
This is just a different approach to get rid of the .then() but you can do this only if you know for sure that you only need those orders fetched once. Otherwise you need to go with a promise. Also if you want to go with this approach you need to prefetch your orders when your app stars something like this:
(function () {
var app = angular.module("ngOrderApp", []);
app.run(['order'], function() {});
}());
So you have some kind of guarantee that your orders will be loaded when you will try to access them. The run function runs when your Angular app kickstars so if you load your factory there the $http will begin fetching your data and populate the orders array so you can use it in your app.
Again, it depends on what do you want to achieve in the end. You can compromise and have a lighter controller but you can risk not getting your fetched data in time (if you have a lot of orders). I wouldn't stress so much over a few extra lines of code as long as they are doing their job and are written correctly.
Hope this information helped.
Good luck!

Related

Proper way to structure my controller, service, and factory in angularjs application...service or factory?

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(...)

Angular response objects and storing the data

I'm trying to take the response of an $http request and save it to a custom cache. I want to then use that cache to display data into the view. I thought the cache would be checked automatically on each request before fetching new data, but that doesn't seem to be working for me.
The problem I'm having is I can't seem to save the data. The following function needs to make 2 requests: articles and images.
getImages: function() {
var cache = $cacheFactory('articlesCache');
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
});
});
return cache;
}
This creates the custom cache, but there's no data in the returned object. I know now that I can't save the data this way, but I don't know why or how I would go about doing it.
Can anyone explain how storing response data works? Where, if at all, does using promises come in here?
Your return statement executes before the code in your then function does. If you want to return the cache you'll want to run everything through the $q service and then return the resolved promise.
This is probably not the best way to use $cacheFactory. Typically you'd expose your cache as a service at a higher level and then access the cache via the service where needed.
So on your main module you'd have something like this to create the cache.
.factory('cache', function ($cacheFactory) {
var results = $cacheFactory('articleCache');
return results;
})
Then where ever you need the cache you inject it into the controller and use cache.get to retrieve the data from it.
If you want to use $q to implement this, your code would look something like the code below. (Disclaimer: I've never used $q with $cacheFactory like this, so without all of your components, I can't really test it, but this should be close.)
var imageService = function ($http, $q,$cacheFactory) {
var imageFactory = {};
imageService.cache = $cacheFactory('articlesCache');
imageFactory.getImages = function () {
var images = $q.defer();
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
images.resolve(cache.get('articlesCache'))
});
});
return images.promise
app.factory('ImageService', ['$http', '$q', '$cacheFactory', imageService]);
});
I adapted the code from this answer: How to get data by service and $cacheFactory by one method
That answer is just doing a straight $http.get though. If I understand what you're doing, you already have the data, you are posting it to your server and you want to avoid making get call to retrieve the list, since you have it locally.

Accessing and using JSON within an Angular service for logic flow

I asked the wrong question yesterday (and got a goodanswer that worked), but am realizing it's not what I needed. I need to be able to retrieve JSON data (preferably once), store it, and access it throughout my service. The challenge I'm having is that all the examples I can find talk about using JSON and passing to the app/controller, whereas in this case I need to get it, check it, and then it dictates what my module/service does.
For instance, I have my App and Controller, and then I have a module such as (this is psuedo-code, not meant to run):
angular.module("myModule")
.service("myService1", function($q, myService2, $http) {
this.getModel = function() {
return {
title: "My Title",
desc: "My Desc"
options: function () {
if (condition A)
return "option1";
else
return "option2";
}
};
};
})
.service("myService2", function($q, $http) {
this.getCfgInfo = function () {
var defer = $q.defer();
$http.get("my/json/url").then(function(response) {
defer.resolve(response.data);
});
return defer.promise;
};
})
In this example, I'm wanting to get the JSON, and use it within myService1 for both literal values (title, desc) as well as for conditions (condition A within the if).
I know I can do something like this (thanks to Joel for helping yesterday):
service("myService1", function($q, myService2, $http) {
// get a promise object for the configuration info
var cfgProm = rtDataMapper.getCfgInfo()
this.getModel = function() {
return {
title: cfgProm.then(function(response) {
return response.JSON_NAME;
}),
and it works fine as I've got the title mapped back into my model and there is a watch(), but I'm stumped as to how I get, store, and use the JSON within the service itself as a conditional (i.e. if (condition A) where condition A is coming from the JSON. Trying to wrap these in .then() doesn't seem to make sense, or at least I can't figure out how to do it.
I'm new to Angular and am attempting to modify some code that was left to us. I'm guessing I don't need the myService2 just to get the JSON. Can anyone help point me in the right direction? I've spent several hours online but can't seem to find a relevant reference/example.
Thanks
Live demo (click).
I'm having the service immediately get the data when it is injected (that code will only run once no matter how many times you inject it). That's nice because you won't have to call a function to get the data - it's called for when creating the service.
Your service method that returns that data will need to return the promise of the data, of course, since you aren't guaranteed that it will have come through when you ask for it. You can pass arguments to that method to use to determine your conditions. All you need to do for that is use promise.then in the method and resolve the promise with the modified data. Since that method is returning the promise already, the modification will be updated on the resolve. See all of this below and in the demo.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.getData(15).then(function(data) {
$scope.myData = data;
});
});
app.factory('myService', function($q, $timeout) {
//this code only runs once when you first inject the service
//get data immediately
var deferred = $q.defer();
$timeout(function() { //simulate ajax call
var data = { //ajax response data
foo: 15,
bar: 'Some data!'
};
data = modifyData(data, 1);
deferred.resolve(data);
}, 500);
function modifyData(data, fooVal) {
if (data.foo === fooVal) {
data.baz = 'Conditional data!';
}
return data;
}
var myService = {
//data can be modified when it comes from the server,
//or any time you call this function
getData: function(fooVal) {
if (fooVal) { //if you want to modify the data
deferred.promise.then(function(data) {
data = modifyData(data, fooVal);
deferred.resolve(data);
});
}
return deferred.promise;
}
};
return myService;
});

AngularJS broadcast from one service not triggering a call to second service

I have defined two AngularJS services ... one is for the YouTube Player API, and other for the YouTube iFrame Data API. They look like this:
angular.module('myApp.services',[]).run(function() {
var tag = document.createElement('script');
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
})
.factory('YtPlayerApi', ['$window', '$rootScope', function ($window, $rootScope) {
var ytplayer = {"playerId":null,
"playerObj":null,
"videoId":null,
"height":390,
"width":640};
$window.onYouTubeIframeAPIReady = function () {
$rootScope.$broadcast('loadedApi');
};
ytplayer.setPlayerId = function(elemId) {
this.playerId=elemId;
};
ytplayer.loadPlayer = function () {
this.playerObj = new YT.Player(this.playerId, {
height: this.height,
width: this.width,
videoId: this.videoId
});
};
return ytplayer;
}])
.factory('YtDataApi', ['appConfig','$http', function(cfg,$http){
var _params = {
key: cfg.youtubeKey
};
var api="https://www.googleapis.com/youtube/v3/";
var yt_resource = {"api":api};
yt_resource.search = function(query, parameters) {
var config = {
params: angular.extend(angular.copy(_params),
{maxResults: 10,
part: "snippet"}, parameters)
};
return $http.get(api + "search?q=" + query, config);
};
return yt_resource;
}]);
(also note that the 'setPlayerId' function of my player service is called by a custom directive ... but that's not important for my question).
So, here's the issue. I need to ensure that the Player API code is loaded before I set the video id and create the player, which is why I have it broadcasting the 'loadedApi' message. And this works great, if I then in my controller pass a hard-coded video id, like this:
function ReceiverCtrl($scope,$rootScope,$routeParams,ytplayer,ytdataapi) {
$scope.$on('loadedApi',function () {
ytplayer.videoId='voNEBqRZmBc';
ytplayer.loadPlayer();
});
}
However, my video IDs won't be determined until I make an API call with the data api service, so I ALSO have to ensure that the results of that call have come back. And that's where I'm running into problems ... if I do something like this:
$scope.$on('loadedApi',function () {
ytdataapi.search("Mad Men", {'topicId':$routeParams.topicId,
'type':'video',
'order':'viewCount'})
.success(function(apiresults) { // <-- this never gets triggered
console.log(apiresults); // <-- likewise, this obviously doesn't either
});
});
Then the interaction with the data service never happens for some reason. I know the data service works just fine, for when I un-nest it from the $on statement, it returns the api results. But sometimes latency makes it so that the results don't come back fast enough to use them in the player service. Any thoughts on what I can do to make the data search after receiving the message that the player API is ready, but still keep the two services as two separate services (because other controllers only use one or the other, so I don't want them dependent on each other at the service level)?
Figured it out; I had to call $scope.$apply(), like this:
function ReceiverCtrl($scope,$rootScope,$routeParams,ytplayer,ytdataapi) {
$scope.$on('loadedApi',function () {
ytdataapi.search("",{'topicId':$routeParams.topicId,'type':'video','maxResults':1,'order':'viewCount'}).success(function(apiresults) {
ytplayer.videoId=apiresults.items[0].id.videoId;
ytplayer.loadPlayer();
});
$scope.$apply();
});
}
Is there anyone who could shed light on why this works, though? $scope.$digest() also works ... but I thought those methods were only used when you need to update bindings because of some javascript code that Angular isn't aware of. Is the nesting I've got here doing that (I wouldn't think it should, as my ytdataapi service is using $http)?

How to wait till the response comes from the $http request, in angularjs?

I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.

Resources