passing data to a controller using service - angularjs

On the code below, I am trying to reference the photos array in the appService service from the pictureCtrl controller, but it won't work well. The only thing that works is $scope.photoService referencing appService when I try to call the model on the HTML. When I try to call currentPhoto model on the HTML it won't show anything.
I would like to get the currentPhoto model on the HTML page referencing the first index of the photos array which will be an object and when I click on the favorite button on HTML it will run the sendFeedback method and will change currentphoto. How can do it?
.controller('pictureCtrl', function ($scope, appService) {
// angular.extend($scope, appService);
$scope.photoService = appService;
$scope.photos = appService.photos;
$scope.currentPhoto = appService.photos[0];
$scope.sendFeedback = function (bool) {
if(bool) {}//add it to favorite
var randomIndex = Math.round(Math.random() * flickrData.photos.length-1);
$scope.currentPhoto = angular.copy($scope.service.flickrData.photos[randomIndex]);
};
})
.factory('appService', function ($http) {
var photoService = {};
photoService.photos = [];
photoService.feelingText = "";
photoService.getFlickrPictures = function (text) {
console.log('this has been called');
return $http({
method: "JSONP",
url: apiUrl+'&text='+text+'&format=json&jsoncallback=JSON_CALLBACK'
}).then(function (data) {
photoService.photos = data.data.photos.photo;
console.log('flickrData', photoService.photos);
}, function(err){
throw err;
});
};
return photoService;
}

It seems that the photos array never has been populated. Since you are using a factory, the thing that will get injected into your controller will be a fresh instance of photoService.
You will only see photos if you first call your function to fetch the data from the $http service.
I would create a function loadPhotos on your service that would return the data as a promise to your controller.

Related

AngularJS Promises: Put promise in Factory so it is globally accessible and *editable*

I'm trying to pull data from an external JSON file and display it for the user to see. Through various actions, the user would then be able to change the data returned from the JSON file, without writing those changes to the file (in this example, incrementing values by one by clicking on a div). I've created a promise service that successfully pulls the data and displays it. I can even get it so the data can be changed in individual controllers.
This is where I get stuck: I cannot find a way to make any changes to the data in the PromiseService, so changes cannot propagate globally. How do I make it that any change in the promise data at the controller level will be reflected in the PromiseService and, thus, reflected in any data binding in the app? I'm new to promises, so I'm open to a completely different approach.
Plunker
HTML:
<body ng-app="pageApp" ng-controller="pageCtrl" nd-model="items">
{{items}}
<div class="button" ng-controller="buttonCtrl" ng-click="incrementValues()">
Click to increment:
<br>{{items}}
</div>
</body>
PromiseService:
pageApp.factory('PromiseService', function($http) {
var getPromise = function() {
return $http.get('items.json').then(function(response) {
return response.data;
});
};
return {
getPromise: getPromise
};
});
Button Controller (Page Controller in Plunker):
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.incrementValues = function()
{
PromiseService.getPromise().then(function(data) {
$scope.items = data;
for(var i = 0; i < data.items.length; i++)
{
data.items[i]['value']++;
}
}).catch(function() {
});
};
});
The incrementValues function works successfully the first time, but each consecutive click re-pulls the promise and resets the data. To sum up: how do I reflect the incremented values in the PromiseService, as opposed to local variables?
You could add to your factory a private property where you store the items. Then create 3 different methods to update and access to that property.
pageApp.factory('PromiseService', function($http) {
var items = {}; // [] in case it is an array
var updateData = function(updatedData){
items = updatedData;
}
var getUpdateData = function(){
return items;
}
var getPromise = function() {
return $http.get('items.json').then(function(response) {
items = response.data;
return response.data;
});
};
return {
getPromise: getPromise,
updateData : updateData,
getUpdateData : getUpdateData
};
});
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.items = [];
//You should call this method to retrieve the data from the json file
$scope.getData = function(){
PromiseService.getPromise().then(function(data) {
$scope.items = data;
}).catch(function() {
});
}
$scope.incrementValues = function(){
for(var i = 0; i < $scope.items.length; i++){
$scope.items[i]['value']++;
}
PromiseService.updateData($scope.items); //This could be skipped in case you do not want to 'store' these changes.
};
});
Then in others controller you could use the same service to retrieve the updated Data like this:
$scope.items = PromiService.PromiseService();
In the future you could also create a new method to update the json itself instead of stored internally
Your function creates a new $http call every time it's called, and thus returns a new promise, encspsulating new data, every time it's called.
You need to return the same promise every time:
var thePromise = $http.get('items.json').then(function(response) {
return response.data;
});
var getPromise = function() {
return thePromise;
};

what is the scope of a service in angularjs?

I'm trying angularjs for the first time and created a service that I use to make ajax calls to my application API and retrieve a paginated list. I called it "GridService". The service is injected into a controller and everything works great! Until I tried to use the same service twice inside two different controllers on the same page. To my horror, the second instance of the service overwrites the first (doh)
For example; I render two partials as follows :
<div id="location_areas" class="container tab-pane fade" ng-controller="areasController" ng-init="initialise()">
#include('areas._areas')
</div>
<div id="location_people" class="container tab-pane fade" ng-controller="peopleController" ng-init="initialise()">
#include('people._people')
</div>
and I inject the service into a controller as follows and link to the service properties
angular.module('areasController', [])
.controller('areasController', function($scope, $attrs, $http, AreaService, GridService, HelperService) {
$scope.areas = function() { return GridService.getListing() }
$scope.totalPages = function() { return GridService.getTotalPages() }
$scope.currentPage = function() { return GridService.getCurrentPage() }
$scope.columns = function() { return GridService.getColumns() }
Lastly, my abbreviated service is as simple as
angular.module('gridService', [])
.provider('GridService', function($http) {
var columns = {};
var filters = {};
var listing = [];
var totalPages = 0;
var range;
var currentPage = 1;
return {
/**
* Get the requested data (JSON format) from storage then populate the class properties which
* are bound to equivalent properties in the controller.
*
* #return void
*/
list : function(url,pageNumber,data) {
url = url+'?page='+pageNumber;
for (var key in data) {
if( angular.isArray( data[key] ) )
{
angular.forEach( data[key], function( value ) {
url = url+'&'+key+'[]='+value;
});
}
else
{
url = url+'&'+key+'='+data[key];
}
}
$http({ method: 'GET', url: url })
.then(function(response) {
listing = response.data.data;
totalPages = response.data.last_page;
range = response.data.last_page;
currentPage = response.data.current_page;
// Pagination Range
var pages = [];
for(var i=1;i<=response.data.last_page;i++) {
pages.push(i);
}
range = pages;
});
},
Obviously I have boobed (doh). Is it possible to create this scenario or have I misunderstood angularjs architecture?
Please update your question with the relevant code for your service. Services in Angular are by definition Singletons. They should not have any private state. If they do have state, it should be state that is means to be shared between two controllers.
If you are just making $http requests in your service, it should just return the promise to the calling controllers -- not causing any "overlap".
UPDATE
It looks like you a missing a few lines from your service. It looks like it contains columns, filters, etc.
So this is the problem, it is a singleton. What you should do it break it up into two different classes, the network layer that still makes the AJAX call to get the data -- and a factory that returns a new instance of your Grid configuration.
gridFactory.$inject = ['$http'];
function gridFactory($http) {
return function(url, pageNumber, data) {
// put your for (var key in data) logic here...
return new Grid($http);
}
}
function Grid($http) {
var self = this;
$http({ method: 'GET', url: url })
.then(function(response) {
self.listings = data.listings;
self.totalPages = data.total_pages;
...
// put your grid config here following this pattern, attaching each to
// the 'self' property which is the prototype of the constructor.
}
}
So now the factory will return a new instance of the 'Grid' object for every time you call the factory. You need to call the factory like gridFactory(url, pageNumber, data);

Controlling order of execution in angularjs

I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}

AngularJS : controlling services data with factory

I am trying to 'control' data in a factory from two separate controllers and while the below code works fine, it doesn't always work. For whatever reason it only binds the data about 50% of the time. Is there a way to make sure that the data is being binded all the time and if either controllers make edits to the data that the changes will be reflected on both ends.
Service:
angular.module('service', ['ngResource']).
factory('Service', function($resource){
return $resource('/api/data/:id', {id:'#id'}, {});
});
Factory:
angular.module('factory', ['ngResource', 'service']).
factory('Factory', function($resource, Service) {
this.mydata = '';
return {
getData: function(id){
return Service.query({id: id});
},
data: function(data){
return this.mydata;
},
setData: function(data){
this.mydata = data;
}
}
});
Controller:
$scope.Factory = Factory;
var items = Factory.getData(0);
items.$promise.then(function(itemArr){
var item = itemArr[0];
$scope.data = item;
Factory.setData(item);
});
If there is a better way to do this so that I don't have to set the data in the factory that would be nice. It would also be nice to not have to deal with the promise object in the controller, but I don't think it would be possible to get the data out in the factory.
After setting the data using the above factory I access it in a different controller with the following code:
var item = Factory.data();
$scope.selected = [{foo:'bar'},{foo1:'bar1'}];
angular.forEach($scope.selected, function(value, key){
item.requirements.push(value);
})
Factory.setData(item);
Ultimately I want to be able to access the same changing data from both controllers. The above works, but only some of the time and I'm not sure whats not getting set.
EDIT:
I was able to get it to work all the time by using the $scope.$watch functionality in the controller on the call back function. The data is bound, but angular needs to know what to watch for as suggested in the answer below.
To not have to manually set the data in the 'factory' (aka angular service), you can just set it in the callback to the resource:
return {
getData: function(id){
return Service.query({id: id}, function(data){
myData = data;
});
},
If you want to not deal with the promise object, you can send in a callback of your own to the getData function and when it is complete, call the callback you send in in the callback of the resource:
return {
getData: function(id, cb){
return Service.query({id: id}, function(data){
myData = data;
if (cb) {cb(data);}
});
},
Which changes the way you call getData to this:
var items = Factory.getData(0, function(itemArr){
var item = itemArr[0];
$scope.data = item;
});

How to use data from $http in other controller

How can I use the totalResults outside of the function that Im setting it? I just cant wrap my head around how to do it, I need to use the totalResults that I gather from my database and use in another function to calculate the amount of pages. I do this so I dont load all the data to the client but I still need to know the total count of rows in the database table.
My json looks like:
Object {total: 778, animals: Array[20]}
Angular:
var app = angular.module('app', []);
app.controller('AnimalController', ['$scope', 'animalSrc', function($scope, animalSrc)
{
$scope.animals = [];
var skip = 0;
var take = 20;
var totalResults = null;
//$scope.totalResults = null;
$scope.list = function()
{
animalSrc.getAll(skip, take, function(data) {
$scope.animals = $scope.animals.concat(data.animals);
// I need to be able to use this outside of function ($scope.list)
totalResults = data.total;
//$scope.totalResults = data.total;
});
};
$scope.showMore = function()
{
skip += 20;
$scope.list();
};
$scope.hasMore = function()
{
//
};
// Outputs null, should be the total rows from the $http request
console.log(totalResults);
}]);
app.factory('animalSrc', ['$http', function($http)
{
// Private //
return {
getAll: function(skip, take, callback)
{
$http({
method: 'GET',
url: 'url' + skip + '/' + take
}).
success(function(data) {
callback(data);
}).
error(function(data) {
console.log('error: ' + data);
});
}
};
}]);
You need to start thinking asynchronously. Your console.log is called before the $http has returned and totalResults has been set. Therefore, totalResults will always be null.
You need to find some way to delay the call to console.log so that the $http call can finish before you run console.log. One way to do this would be to put the console.log call inside your callback function so that it is definitely called after $http's success.
A more elegant way to do this is to use promises. angular.js implements $q, which is similar to Q, a promise library.
http://docs.angularjs.org/api/ng.$q
Instead of creating a callback function in getAll, you return a promise. Inside $http success, you resolve the promise with the data. Then, in your controller, you have a function that is called when the promise is resolved. Promises are nice because they can be passed around and they allow you to control the flow of your asynchronous code without blocking.
Here's a boilerplate I was just working on for myself for similar setup where data is an object that needs to be split into more than one scope item. Issue you weren't grasping is storing the data within the service, not just using service to retrieve data. Then the data items are available across multple controllers and directives by injecting service
app.run(function(MyDataService){
MyDataService.init();
})
app.factory('MyDataService',function($http,$q){
var myData = {
deferreds:{},
mainDataSchema:['count','items'],
init:function(){
angular.forEach(myData.mainDataSchema,function(val,idx){
/* create deferreds and promises*/
myData.deferreds[val]=$q.defer();
myData[val]= myData.deferreds[val].promise
});
/* load the data*/
myData.loadData();
},
loadData:function(){
$http.get('data.json').success(function(response){
/* create resolves for promises*/
angular.forEach(myData.mainDataSchema,function(val,idx){
myData.deferreds[val].resolve(response[val]);
});
/* TODO -create rejects*/
})
}
}
return myData;
})
app.controller('Ctrl_1', function($scope,MyDataService ) {
$scope.count = MyDataService.count;
$scope.items =MyDataService.items;
});
app.controller('Ctrl_2', function($scope,MyDataService ) {
$scope.items =MyDataService.items;
$scope.count = MyDataService.count;
});
Plunker demo

Resources