Angular - update controller data from another controller - angularjs

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/

Related

Update model with $http using a service not triggering model change

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.

angular controller needing refresh to pickup new data in a factory promise

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
})
}])

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 would I re-instantiate an AngularJS controller after a change in data?

I have looked, and assume this is simple, but just couldn't figure out the API documentation for this.
Assume I have a controller that pulls data when first called (I'm leaving out a ton, of course):
myCtrl = function ($scope, Data) {
$scope.data = [];
data_promise = Data.getData(); //a $http service
data_promise.success(function (data) {
$scope.data = data;
});
}
That works great, and when the page loads I get $scope.data populated exactly as I need it. However, of course, the user may wish to update the data. Assume a simple service "Data.save()" called when a server clicks a "save" button on a form:
myApp.factory('Data', function ($http) {
save: function (data) {
$http({
method: 'POST',
url: 'someURL',
data: data,
}).success(function(){
//something here that might trigger the controller to refresh
});
};
});
What would I put in the success callback that might re-instantiate the controller so that it has the most up-to-date data from the server? Currently I am having to refresh the page to get the updated data. I am not worried right now about minimizing server calls by cashing results and changes. I just need to get this to work first.
Thanks!
You do not need to refresh. Simply change the updated data in the ControllerScope.
This should work.
myApp.factory('Data', function ($http) {
save: function (data, $scope) {
$http({
method: 'POST',
url: 'someURL',
data: data,
}).success(function(){
$scope.data = newData;
//something here that might trigger the controller to refresh
});
};
});
// in your controller
Data.save(data, $scope);
But: You shouldn't do this way. This looks messy. Use services or events which you watch to wait for the changes comming back from the service.
OK, although I am sure there is a better answer I have one for me. Essentially I am taking the important parts of the controller and placing them in the success callback. In order to keep it from looking messy, I have wrapped all the parts of the controller that need be updated in a named function.
myCtrl = function ($scope, Data, $q) {
// Binding the Data
var updateAll;
updateAll = function () {
$scope.data1 = [];
$scope.data2 = [];
$scope.data3 = [];
$scope.data4 = [];
service_promise1 = Data.getData1(); //a $http service
service_promise2 = Data.getData2();
service_promise3 = Data.getData3();
service_promise4 = Data.getData4();
$q.all([service_promise1,service_promise2,service_promise3,service_promise4])
.then(function([service1,service2,service3,service]){
$scope.data1 = service1 + service2 //I know this isn't valid Javascript
// just for illustration purposes
$scope.data2 = service2 - service1 + service 3
//etc...
});
};
updateAll();
//Form Section
$("#myForm').dialog({
buttons: {
Save: function () {
Data.save(data).success(function(){
updateAll();
});
}
}
});
}
Breaking this down, I have wrapped all the assignments to scope objects that rely on services into the updateAll function and invoke it on instantiation of the myCtrl. In the form that updates the data I call the updateAll() function upon success of the Data.save() function.
Not exactly brain surgery, I'll admit, but I had gotten confused with $scope.$apply() and thinking about just calling myCtrl(). That somehow seemed like the "Angular" way, but neither worked. I guess the controller function gets run only once on page refresh and there is no Angular way to call it again.

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