I have the following model:
var MODEL = { myTable: [], anotherTable: [] }; //--- initially set as empty arrays
MODEL is a global variable as it is used to bind the app model in different controllers.
I have devined a service that updates the model, getting the data from a php page that returns the table rows:
myApp.factory('myAppUtils', function($http) {
return {
updateModelTable: function(tableName) {
var pageToCall = "";
var params = "";
switch(tableName){
case 'myTable':
pageToCall = "myTable.php";
params = "";
break;
case 'anotherTable':
break;
default:
break;
}
var updateModel = function(tableName, data){
MODEL[tableName] = data;
};
var url = "/" + pageToCall;
$http.get(url).
success(function(data, status, headers, config) {
updateModel(tableName, data);
}).
error(function(data, status, headers, config) {
alert("error");
});
}
};
});
and then one of the controllers:
myApp.controller("MyTableCtrl", function($scope, myAppUtils){
$scope.table = MODEL.myTable;
myAppUtils.updateModelTable("playersTable");
});
once I run the app, the myAppUtils.updateModelTable() function is correctly invoked, it does update successfully the MODEL but the view controlled by MyTableCtrl does not update accordingly.
What I'm trying to do is to set up some sort of CRUD operations, getting and setting values from a sql db. I'd like to, for example, insert a new record and then call the update function to update the model and consequently the view, but this is not happening.
Is this the correct "angular" way to do the thing?
Any hint? What am I doing wrong?
Thanks in advance
Instead of using a global variable to share data between controllers, you can use a separate service to cache data, as Angular Service is singleton.
So in your case, create a service
myApp.service('modelService',function() {
var model = {
myTable: [],
anotherTable: []
};
return model;
});
Then in your factory, inject this service and change your updateModel to
var updateModel = function(tableName, data){
modelService.myTable = data;
};
And in your controller, inject modelService and bind it to $scope as
$scope.model = modelService;
Finally, in your view, you can access model.myTable, which can be updated automatically once you update them in your factory.
Related
Is it possible to add values to my $resource $cacheFactory from my controller & keep this data in sync across multiple controllers? Essentially what I'm trying to accomplish is:
1) pull JSON resource from API
2) manipulate JSON as if it weren't a $resource anymore, just a plain JSON object that I can use between controllers.
Is there an "angular way" to do this or should I just cache the whole place list using local storage & read and write everything else from there?
.factory('places', ['$resource','environment','$cacheFactory',
function($resource, environment, $cacheFactory) {
var cache = $cacheFactory('places');
return $resource(environment.apis.places, {}, {
query: {
isArray:true,
method: 'GET',
cache: cache
}
});
}
])
.controller('ItemCtrl', function($scope, places) {
places.query({}, function(result){
$scope.item = result[0]
})
$scope.makeFav = function(index){
//add a new key to cached places data
$scope.item.fav = true
}
}
.controller('ListCtrl', function($scope, places) {
places.query({}, function(result){
$scope.item = result //should get the updated cache changed by ItemCtrl
})
console.log($scope.item[0].fav) //should = true
}
Use the following process:
Create a constant recipe
Inject cacheFactory
Create an instance of cacheFactory
Inject the constant into each controller
Reference the instance of cacheFactory
Manipulate the instance of cacheFactory in each controller
function sharedCache($cacheFactory)
{
var sharedCache = $cacheFactory.get('sharedCache') ? $cacheFactory.get('sharedCache') : $cacheFactory('sharedCache');
return sharedCache;
}
function bar(sharedCache, $scope)
{
sharedCache.put('config', $scope.config);
}
bar.$inject = ['sharedCache', '$scope'];
sharedCache.$inject = ['$cacheFactory'];
angular.module('foo',[]);
angular.module('foo').constant('sharedCache', sharedCache);
angular.module('foo').controller('bar', bar);
References
AngularJS Documentation for iid
ng-book: caching through HTTP
I'm just starting to use AngularJS.
I have a simple CRUD app which communicates to REST api. I have two controllers which control my Projects data and Tasks data respectfully. On the backend Tasks are liked by foreign key to the parent Project. So when I delete a Project the associated Tasks are also deleted (this is the functionality I want right now).
So everything works except that when I delete a Project I want to reload the Tasks list. Basically after ConcernService.del('projects/', item) is called I want the Tasks list to be refreshed from the API. I know this should be handled via the ConcernsService, but I'm not sure of the best way.
// --- CONCERNS FACTORY --- //
concernsApp.factory('ConcernService', function ($http, $q) {
var api_url = "/path/to/api/";
var ConcernService = {
list: function (items_url) {
var defer = $q.defer();
$http({method: 'GET', url: api_url + items_url}).
success(function (data, status, headers, config) {
defer.resolve(data);
}).error(function (data, status, headers, config) {
defer.reject(status);
});
return defer.promise;
},
del: function(item_url, obj) {
return $http.delete(api_url + item_url + obj.id + '/');
},
};
return ConcernService;
});
// --- PROJECTS CONTROLLER --- //
concernsApp.controller('ProjectsCtrl', function ($scope, $http, ConcernService) {
// get all projects
$scope.projects = ConcernService.list('projects/');
// assign the delete method to the scope
$scope.deleteItem = function(item) {
ConcernService.del('projects/', item).then(function(){
// reload projects
$scope.projects = ConcernService.list('projects/');
});
};
});
// --- TASKS CONTROLLER --- //
concernsApp.controller('TasksCtrl', function ($scope, $http, ConcernService) {
// get all tasks
$scope.tasks = ConcernService.list('tasks/');
// assign the delete method to the scope
$scope.deleteItem = function(item) {
ConcernService.del('tasks/', item).then(function(){
// reload projects
$scope.tasks = ConcernService.list('tasks/');
});
};
});
Instead of a generic service, you could have a service that is more specific to your project and that service could contains the model (projects and tasks). When it would update internally, the watchers from each controller would trigger and update their data.
When you want to share the model between controllers, the model should be kept in a service and you should use getter and setter to access it.
This acticle use an older version of Angular, but it will explain you how to use the service as your model.
http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/
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;
});
I have two controllers, to add Item and to delete Item, and a Model to show all items.
This model is injected into the controller ( on working on same template).
Whenever an item is added, I broadcast a message, which is listened by Model and it reloads the data from server.
Code:
ItemModule.factory('ItemListModal', function ($resource, $rootScope){
var allItem = $resource('item/page/:pageId.json', {'pageId': pageId });
var items = allItem.query();
$rootScope.$on('ItemAdded',function(){
items = allItem.query();
});
return items;
});
//Item is another Model, used to send data on server.
function CreateItemCtrl($scope, $rootScope, Item) {
$scope.save = function() {
Item.save($scope.item, function(data) {
$scope.result = data;
$rootScope.$broadcast('ItemAdded');
}, function(data) {
$scope.result = data.data;
});
}
}
function ListItemCtrl($scope, ItemListModal) {
$scope.allItems = ItemListModal;
}
Issue: Now since the dependency on ListItemCtrl is already resolved when template was first loaded, on adding Item it only changes the Model, but this is not re-injected into the ListItemCtrl. And due to this, the list on template do not change.
Is there any way to tell AngularJS to re-resolve the controller's dependency?
I really don't want to listen for event in Controllers and re-query data there, as there are other controllers which also needs same data from server.
Add another level of indirection on what you return from your service.
ItemModule.factory('ItemListModal', function ($resource, $rootScope){
var allItem = $resource('item/page/:pageId.json', {'pageId': pageId });
var data = {items:allItem.query()};
$rootScope.$on('ItemAdded',function(){
data.items = allItem.query();
});
return data;
});
function ListItemCtrl($scope, ItemListModal) {
$scope.allItems = ItemListModal;
// use as $scope.allItems.items wherever you need it. It will update when changes occur.
}
But it might be better to have a canonical representation of the item list on the client, and work to keep that current when you add things (just saving it to the server quietly).
The issue seems to be that while item is getting updated (have you tried console.log in the $on?) it's not an object and so hasn't been passed by reference. If you switch around your service to this:
ItemModule.factory('ItemListModal', function ($resource, $rootScope){
var ItemListModalScope = this;
var allItem = $resource('item/page/:pageId.json', {'pageId': pageId });
ItemListModalScope.items = allItem.query();
$rootScope.$on('ItemAdded',function(){
ItemListModalScope.items = allItem.query();
});
return ItemListModalScope;
});
And then wherever you use your allItems in your dome, you would do
{{ allItems.items }}
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