AngularJS Object not updating - javascript

I have an object like this:
.controller('lineCtrl', function($sessionStorage, $rootScope, $scope, $stateParams, $state, $filter, config, $http, SCAN_EVENT, $ionicLoading, $ionicPopup, ScanService){
var areaName;
var lineCounts;
function filterObjects () {
switch ($stateParams.area) {
case 'PW':
$scope.items = $filter('filter')($rootScope.runnerItemsPW, {line: $stateParams.line}, true);
areaName = "Putwall";
lineCounts = $rootScope.lineCountPW;
break;
case 'NC':
$scope.items = $filter('filter')($rootScope.runnerItemsNC, {line: $stateParams.line}, true);
areaName = "Non-Con";
lineCounts = $rootScope.lineCountNC;
break;
case 'RE':
$scope.items = $filter('filter')($rootScope.runnerItemsRE, {line: $stateParams.line}, true);
areaName = "Receiving";
lineCounts = $rootScope.lineCountRE;
break;
case 'SP':
$scope.items = $filter('filter')($rootScope.runnerItemsSP, {line: $stateParams.line}, true);
areaName = "Singles Pack";
lineCounts = $rootScope.lineCountSP;
break;
default:
break;
}
}
filterObjects();
$rootScope.lineData = {
position: 1,
lineNumber: $stateParams.line,
area: $stateParams.area,
areaName: areaName,
lineCounts: lineCounts,
items: $scope.items
};
$scope.clearLine = function(){
ScanService.dispatch(String("00" + $rootScope.lineData.lineNumber).slice(-2) + 'DONE');
};
$scope.removeStationItems = function (stationdID, createdDateTime){
var url = config.baseURL + "/runner/removeDesktopRunnerItem/" + stationdID + "/" + createdDateTime.split(' ').join('_') + "/" + $sessionStorage.user.loginName;
$http.get(url).then(function(response){
if(response.status === 200 && response.statusText === "OK"){
var data = response.data;
if(data === ''){
$state.go($rootScope.prevState);
$rootScope.setObjects(data);
filterObjects();
}else{
$rootScope.setObjects(data);
filterObjects();
var found = $filter('getByStationID')(data.runnerItems, $rootScope.lineData.lineNumber);
if(found === null){
$state.go($rootScope.prevState);
}
}
}
})
};
})
.controller('queue', function ($rootScope, $http, $filter, $scope, config, $interval, $ionicLoading) {
$rootScope.setObjects = function (data){
$rootScope.runnerItemsPW = $filter('filter')(data.runnerItems, {areaShort: "PW"});
$rootScope.lineCountPW = $filter('filter')(data.lineCounts, {areaShort: "PW"});
$rootScope.runnerItemsRE = $filter('filter')(data.runnerItems, {areaShort: "RE"});
$rootScope.lineCountRE = $filter('filter')(data.lineCounts, {areaShort: "RE"});
$rootScope.runnerItemsNC = $filter('filter')(data.runnerItems, {areaShort: "NC"});
$rootScope.lineCountNC = $filter('filter')(data.lineCounts, {areaShort: "NC"});
$rootScope.runnerItemsSP = $filter('filter')(data.runnerItems, {areaShort: "SP"});
$rootScope.lineCountSP = $filter('filter')(data.lineCounts, {areaShort: "SP"});
};
});
When the controller is first started the $scope.items is added to the object and works just fine. The line $scope.items is updated when new data comes in. However, the ng-repeat I have for lineData.items does not update.

$filter creates a new object so need to updated $rootScope.lineData. Update the $rootScope.lineData again after filter.

You are breaking the original object reference when you reassign $scope.items so the original reference passed to the lineData object is no longer the same as the array referenced by $scope.items
Simple example
var myArray = [1,2,3];
var obj={
items: myArray // reference to array above
}
console.log(myArray ); // [1,2,3]
console.log(obj.items); // [1,2,3] - exact same object reference as myArray
// now reassign myArray with new object reference
myArray =[6,7,8];
console.log(myArray ); // [6,7,8]
console.log(obj.items); // [1,2,3] - the original reference hasn't changed
// now reassign object property value to new array
obj.items = myArray ;
console.log(obj.items); // [6,7,8] - references new array
NOTE: you should be using a service to share data and methods across controllers.

You are only assigning to $rootScope.lineData once, in the controller initialization function. This function only gets called once, during the bootstrap process.
You need to assign it in your filterObjects function as well so that it gets updated by your $http call. This means that you need to create it before calling filterObjects the first time. Like this:
$rootScope.lineData = {
position: 1,
lineNumber: $stateParams.line,
area: $stateParams.area,
areaName: areaName,
lineCounts: lineCounts,
};
function filterObjects () {
switch ($stateParams.area) {
case 'PW':
$rootScope.lineData.items = $scope.items = $filter('filter')($rootScope.runnerItemsPW, {line: $stateParams.line}, true);
areaName = "Putwall";
lineCounts = $rootScope.lineCountPW;
break;
// etc.
}
}
filterObjects();

In $http response code add line $scope.$apply(); after calling function filterObjects()

Related

Callback from Angularjs factory

I want to get a callback from a factory. If I understand correctly, the callback should be in the deepest nested function (i.e. under var myResult = $filter('filter')(myData, {id:exnum})[0];, but I get "TypeError: callback is not a function".
My factory calls another factory, gets a value and injects it into a third one for the final result. This final result logs correctly to console, but I cannot callback to the controller.
Any feedback would be appreciated.
angular.module('resourceFetch', [])
.factory('ResourceFetch', ['JsonService', 'UserProgress', '$filter', function(JsonService, UserProgress, $filter) {
var resourceResult = {};
resourceResult.getResource = function(callback){
UserProgress.getProgress(function(exnum, callback) {
JsonService.get(function(data){
var myData = [];
var myData = data.exercises;
var myResult = [];
var myResult = $filter('filter')(myData, {id:exnum})[0];
console.log(myResult) // <- this displays correctly
callback(myResult); // <- "TypeError: callback is not a function"
});
});
//callback(myResult); <- here "myResult is not defined"
};
return resourceResult;
}]);
This is the controller:
myApp.controller('ResourceFetchTest', function($scope, ResourceFetch) {
$scope.myresults = ResourceFetch.getResource(function(obj1){
console.log('obj1 is ' + obj1);
$scope.MyData = obj1;
$scope.MySelectedData = obj1.string1;
});
});
You could use a promise to return the object
Something like:
angular.module('resourceFetch', [])
.factory('ResourceFetch', ['JsonService', 'UserProgress', '$filter','$q', function(JsonService, UserProgress, $filter,$q) {
var resourceResult = {};
resourceResult.getResource = function(){
var defer = $q.defer();
UserProgress.getProgress(function(exnum) {
JsonService.get(function(data){
var myData = [];
var myData = data.exercises;
var myResult = [];
var myResult = $filter('filter')(myData, {id:exnum})[0];
console.log(myResult) // <- this displays correctly
defer.resolve(myResult);
});
});
return defer.promise;
};
return resourceResult;
}]);
and in the controller:
myApp.controller('ResourceFetchTest', function($scope, ResourceFetch) {
$scope.myresults = ResourceFetch.getResource().then(function(obj1){
console.log('obj1 is ' + obj1);
$scope.MyData = obj1;
$scope.MySelectedData = obj1.string1;
});
});
here's the documentation on promises:https://docs.angularjs.org/api/ng/service/$q
Let me know if you have questions I had the simular problem today and fixed it this way
Sorry, my previous answer was not looking at your code properly, your biggest issue here is that you are trying to pass services within services and it makes your code hard to follow.
What you should do is inject all of your services to your controller module and then you can do something along the lines of this.
myApp.controller('ResourceFetchTest', function($scope, ResourceFetch) {
$scope.dataForProgress = "Bind whatever data in needed to get your progress";
$scope.dataForJson = UserProgress.getProgress(dataForProgress);
$scope.myResults = JsonService.get(dataForJson);
});
Depending on what each service does and what it calls it is possible you are also making Async calls in which case I would recommend looking into the $q directive angular provides.

Dynamically set data on Angular-chart.js

To be honest I am a bit new to angularjs, so this may be problem with my fundamental understanding of angular, rather than angular-charts.
I have two controllers (PieItemsCtrl and PieCtrl) and I would like to communicate between them by using a factory service (called pieItems)
On the one hand the pieItems works as designed in the PieItemsCtrl.
ie:
$scope.slices = pieItems.list();
Whenever something changes in the pieItems service (ie another element is added), then the HTML is automatically updated via a repeater :
<div ng-repeat="(key, val) in slices">
However in the PieCtrl I have this line, and i would expect the pie chart to update automatically :
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
It seems to set these data values upon loading/initialisation of the PieCtrl and that's it. Whenever the pieItems data changes these scope values are not updated.
The source of the two controller and factory object are below. And I also made an unworkable fiddle, incase that helps
PieItemsCtrl :
app.controller('PieItemsCtrl', function($scope, $http, $rootScope, pieItems) {
$scope.slices = pieItems.list();
$scope.buttonClick = function($event) {
pieItems.add(
{
Name: $scope.newSliceName,
Percent: $scope.newSlicePercent,
Color: $scope.newSliceColor
}
)
}
$scope.deleteClick = function(item, $event) {
pieItems.delete(item);
}
}
)
PieCtrl :
app.controller("PieCtrl", function ($scope, $timeout, pieItems) {
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
});
pieItems :
app.factory('pieItems', function() {
var items = [];
var itemsService = {};
itemsService.add = function(item) {
items.push(item);
};
itemsService.delete = function(item) {
for (i = 0; i < items.length; i++) {
if (items[i].Name === item.Name) {
items.splice(i, 1);
}
}
};
itemsService.list = function() {
return items;
};
itemsService.labelsItems = function() {
var a = ['x', 'y'];
for (i = 0; i < items.length; i++) {
a.push(items[i].Name);
}
return a;
};
itemsService.data = function() {
var a = [50,50];
for (i = 0; i < items.length; i++) {
a.push(items[i].Percent);
}
return a;
};
return itemsService;
});
The controller doesn't notice when the value in your factory changes. To include your item-Array in an Angular digest-cycle, tell Angular to $watch that Array.
If you don't want to expose the Array, create a getter:
itemsService.get = function() { return items; }
Then you can include that getter in your $watch expression in your controller:
$scope.$watch(getItems, watcherFunction, true);
function getItems() {
return pieItems.get();
}
The getItems-Function gets called on digest cycle and fires the watcherFunction if the value changed and has the newData as argument. true as 3rd argument creates a deep watch.
function watcherFunction(newData) {
console.log(newData);
// do sth if array was changed
}
For more complex objets, you can use a $watchCollection.

Why does another variable change in AngularJS controller?

I have a AngularJS controller like below:
schedule.controller('schedule', ['$scope', '$http', function($scope, $http){
$http.get('/zt-api/business/admin/' + window.location.pathname.split('/')[2]).success(function(data){
$scope.admin_times = data;
$scope.admin_times_unix = data;
$scope.weekDays = {'Saturday': "شنبه", 'Sunday': "یکشنبه", 'Monday': "دوشنبه", 'Tuesday': "سه شنبه",
'Wednesday': "چهارشنبه", 'Thursday': "پنجشنبه", 'Friday': "جمعه"};
angular.forEach($scope.admin_times, function (value, key) {
angular.forEach(value, function (value2, key2) {
angular.forEach(value2, function (value3, key3) {
angular.forEach(value3, function (value4, key4) {
angular.forEach(value4, function (value5, key5) {
var info = $scope.admin_times[key]["week_"+ key][key3].times[key5];
if (!isNaN(info)){
var myObj = $.parseJSON('{"date_created":"'+ $scope.admin_times[key]["week_"+ key][key3].times[key5] +'"}'),
myDate = new Date(1000*myObj.date_created);
$scope.admin_times[key]["week_"+ key][key3].times[key5] = myDate.toLocaleString().split(", ")[1]
}
});
});
});
});
});
});
}]);
I change $scope.admin_times, but I don't know why $scope.admin_times_unix also changes!
Can you help me?
Because you are assigning same reference of data to admin_times as well as admin_times_unix object.
That's why changing in one object is, affecting other object.
You could solve this issue by using angular.copy which does create a new reference rather than assigning its actual reference.
$scope.admin_times_unix = angular.copy(data);

$http inside $http in an angular factory

I have an angular factory that makes an $http call with a get and then.
.factory('DataModel', function($http) {
I have a .get.then that works great. The value comes back, and since I originally returned a function to return the factory value, everything updates when it changes.
Now I have to make a dependent call based on the data that returned the first time.
First try: $http.get.then inside the outer $http.get.then.
The inner (dependent) call successfully gets the data, but when it updates the factory parameters only the first .get.then is picked up by the calling controller.
Next try: $scope.$watch.
angular.module('starter.services', [])
.factory('DataModel', function($scope, $http) {
If I put a $scope parameter in there I get an error:
Unknown provider: $scopeProvider <- $scope <- DataModel
So I can't seem to use the $scope.$watch method.
Third try: callbacks?
I'm afraid that if I use a callback approach I'll get the data back, but it won't update just like my nested get.then. didn't update.
Here is my full factory:
angular.module('starter.services', [])
.factory('DataModel', function($http) {
var days = {};
var todaysFlavorIndex = 32;
var todaysFlavorName = [32, 'Loading ...', "vanilla_chocolate_chip.jpg"];
var daysLeftCalendar = [];
var flavors = [];
// calendar objects
$http.get("https://jsonblob.com/api/5544b8667856ef9baaac1")
.then(function(response) {
var result = response.data;
days = result.Days;
var dateObj = new Date();
var day = dateObj.getDate();
var endOfMonthDate = new Date(new Date().getFullYear(), dateObj.getMonth(), 0).getDate();
for (var di = day; di <= endOfMonthDate; di++) {
var flavor = days[di - 1];
daysLeftCalendar.push(flavor[1]);
}
var todaysFlavorIndex = -1;
// $scope.$watch('todaysFlavorIndex', function() {
// // Http request goes here
// alert('updating !');
// });
for (var i = 0; i < days.length; i++) {
if ((days[i])[0] == day) {
todaysFlavorIndex = (days[i])[1];
}
}
// flavors
$http.get("https://jsonblob.com/api/55450c5658d3aef9baac1a")
.then(function(resp) {
flavors = resp.data.flavors;
todaysFlavorName = flavors[todaysFlavorIndex];
});
}); // end then
return {
getDays: function() {
return days;
},
getMonth: function() {
return days;
},
getFlavors: function() {
return flavors;
},
getTodaysFlavorIndex: function() {
return todaysFlavorIndex;
},
getTodaysFlavorName: function() {
return todaysFlavorName; // flavors[todaysFlavorIndex];
},
today: function() {
var dateObj = new Date();
var day = dateObj.getUTCDate();
return todaysFlavorIndex;
},
remainingFlavorIndexes: function() {
return daysLeftCalendar
}
};
})
Firstly , services has no $scope.
So injecting scope in factory will always throw you exceptions.
Secondly , try to catch callback from controller instead of factory
Try like this
angular.module('starter.services', [])
.factory('DataModel', function($http) {
return {
myFunction: function() {
return $http.get("https://jsonblob.com/api/5544b8667856ef9baaac1");
}
}
})
.controller("myCtrl", function($scope, DataModel) {
DataModel.myFunction().then(function(result) {
// success
// put your code here
}, function(e) {
// error
});
})
Thirdly, If you wanna have inner $http you can use $q
Try like this
angular.module('starter.services', [])
.factory('DataModel', function($http) {
return {
myFunction: function() {
return $http.get("https://jsonblob.com/api/5544b8667856ef9baaac1");
},
myFunction2: function() {
return $http.get("https://jsonblob.com/api/55450c5658d3aef9baac1a");
}
}
})
.controller("myCtrl", function($scope, DataModel, $q) {
$q.all([
DataModel.myFunction(),
DataModel.myFunction2()
]).then(function(data) {
console.log(data[0]); // data from myFunction
console.log(data[1]); // data from myFunction2
});
});

How do I refactor a calendarViewModel such that it's not tied to a specific controller?

For a small Angular.js testbed project, I've set up the following plunker:
My Plunked Plunker
Originally, I had a local version of this testbed working, when the calendarViewModel was directly included in the Angular controller.
appControllers.controller('PageController', [
'$scope', '$http', 'Enums', 'ViewModels',
function ($scope, $http, Enums, ViewModels) {
var calendarViewModel = function () {
var pub = {};
pub.date = new Date();
pub.isOpen = false;
pub.today = function () {
if(pub.isOpen)
pub.date = new Date();
};
pub.clear = function () {
if(pub.isOpen)
pub.date = null;
};
pub.hide = function () {
pub.isOpen = false;
};
pub.toggle = function ($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.hideCalendars();
pub.isOpen = !pub.isOpen;
};
return pub;
};
// Backing model for this 'controller'
$scope.viewModel = {
// Properties:
startCalendar: new calendarViewModel(),
endCalendar: new calendarViewModel(),
// data:
// Generates an object that is sent to the server with $http calls.
data: function () {
var object = {
startDate: startCalendar.date.toString(),
endDate: endCalendar.date.toString()
};
return JSON.stringify(object);
}
};
// - Controller-specific functions... ----------------------------------
$scope.hideCalendars = function () {
$scope.viewModel.startCalendar.hide();
$scope.viewModel.endCalendar.hide();
};
$scope.clear = function () {
$scope.viewModel.startCalendar.clear();
$scope.viewModel.endCalendar.clear();
};
$scope.today = function () {
$scope.viewModel.startCalendar.today();
$scope.viewModel.endCalendar.today();
};
// Restricts certain days from being selected.
$scope.disableWeekends = function (date, mode) {
return mode === 'day'
&& (date.getDay() === Enums.DaysOfTheWeek.Sunday
|| date.getDay() === Enums.DaysOfTheWeek.Saturday);
};
// This is a demonstration scope action. Pretty much, the pattern
// I found, is to have a view model expose a method that creates
// a stringified JSON blob that we can send to the server. This
// method is how such a save function would work.
$scope.save = function () {
var promise = $http({
method: 'POST',
url: '/some/server/url',
data: $scope.viewModel.data()
});
promise.success(function (data) {
// Do something with the returned data?
}).error(function (data) {
// Do something with the error data?
});
};
// - End of Controller-specific functions... ---------------------------
// Picker-specific options...
$scope.dateOptions = {
'starting-day': Enums.DaysOfTheWeek.Monday,
'format-day': 'dd',
'format-month': 'MM',
'format-year': 'yyyy',
'min-mode': Enums.PickerMode.Day,
'max-mode': Enums.PickerMode.Year
};
$scope.format = 'MM/dd/yyyy';
$scope.today();
}
]);
Since I refactored it out to the ViewModels constant object, though, I get the following errors from Angular:
TypeError: undefined is not a function
at Object.pub.toggle (http://run.plnkr.co/AKUBdEb5M3KT5DM9/app.services.js:31:4)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:10185:21
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:17835:17
at Scope.$eval (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:11936:28)
at Scope.$apply (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:12036:23)
at HTMLInputElement.<anonymous> (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:17834:21)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:2613:10
at forEach (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js:310:20)
The reason I am doing this, is because in theory multiple controllers could have need of calendars that are tied to calendarViewModels (this is why I created the calendarViewModel functional object in the first place.) I want the calendarViewModel construct to not be tied to a specific controller, however, which I have apparently broken by refactoring it in this way.
I think I'm on the right track, but in any event, something is clearly missing. My question: what is the correct way for me to refactor my calendarViewModel, that works and is more easily reusable?
So a few things from your plunker:
Don't use app.constant to do factories. Use app.factory instead, eg:
_
appServices.factory('ViewModels', function() {
var pub = {};
pub.date = new Date();
pub.isOpen = false;
pub.today = function () {
if(pub.isOpen)
pub.date = new Date();
};
pub.clear = function () {
if(pub.isOpen)
pub.date = null;
};
pub.hide = function () {
pub.isOpen = false;
};
pub.toggle = function ($event) {
$event.preventDefault();
$event.stopPropagation();
//hideAll();
pub.isOpen = !pub.isOpen;
};
return pub;
});
Your factories are automatically shared between controllers when you do this:
_
appControllers.controller('FirstController', [ '$scope', 'MyCalendarService', function($scope, MyCalendarService){
$scope.myCalendarService = MyCalendarService;
}]);
appControllers.controller('SecondController', [ '$scope', 'MyCalendarService', function($scope, MyCalendarService){
$scope.myCalendarService = MyCalendarService;
}]);
... if the controllers are defined in parallel in the html. If they are nested, you just need to inject your service at the top level. Ideally, you just need to DI a couple of services in a controller and assign them to the scope.
Does that answer your question?
PS: hideAll is not defined in your plunker, I commented it out and things started to work.
Edit: This edited plnkr should do what you want: http://plnkr.co/edit/7VDYDQhK2CDGnwa8qhWf?p=preview

Categories