angular ionic fails to update for some variables - angularjs

In my Ionic / Angular framework I update 2 variables inside a service (an $http.get().then() block):
for( var di = day; di <= endOfMonthDate; di++) {
var flavor = days[di - 1];
daysLeftCalendar.push( flavor[1]); // dates right away !
}
var todaysFlavorIndex = -1;
for (var i = 0; i < days.length; i++ ) {
if ((days[i])[0] == day) {
todaysFlavorIndex = (days[i])[1];
todaysFlavorName = flavors[todaysFlavorIndex]; // only updates if you change tabs
}
}
Then I have these accessor methods in my service that get called by my controller:
return {
// both of these are hit after switching to one of the two tabs which both reference these functions
remainingFlavorIndexes: function() {
return daysLeftCalendar
},
getTodaysFlavorName: function() {
return todaysFlavorName
}
};
Then in my only controller I expose these variables like this:
$scope.remainingFlavorIndexes = Calendar.remainingFlavorIndexes(); // this one copies over right away !!
$scope.todaysFlavorName = Calendar.getTodaysFlavorName(); // this one doesn't
Then in my view:
<div> <!-- this one shows up right away -->
{{remainingFlavorIndexes}}
</div>
<div> <!-- these two only show up after switching tabs and returning -->
<img class="scaled-image" src="img/{{todaysFlavorName[2]}}">
</div>
<div style="text-align: center;">
{{todaysFlavorName[1]}}
</div>
How is it that I'm handling these 2 variables exactly the same, but todaysFlavorName is empty (even after the .then call returns)?
And why is it that when I switch tabs and come back they are populated?
Edit:
What is supposed to go into remaining flavor indexes is something like this:
[21,20,13,0,27,12,9,18,1,3,30,29,25,7,6,4,9,18,21,13]
And it works every time.
What is supposed to go into todaysFlavorName is:
[21, "peanut butter", "peanut_butter.jpg", "some meaningless text here"]
And it works only after I switch tabs.

There is a big difference between both. In the case of daysLeftCalendar:
the service has an array
the controller calls the service function that returns a reference to that array
the http callback function pushes elements to this array
So, the controller has a reference to the array that is in the service. Whenever the array is modified in the service, it's also modified in the controller, since the controller and the service both share a reference to the same array.
In the case of todaysFlavorName:
the service defines a variable todaysFlavorName referencing an array
the controller calls the service function that returns a reference to that array
the http callback function doesn't modify this array. It assigns a new array to the variable todaysFlavorName.
So, in the end, the controller has a reference to the original array, whereas the service has a reference to the new array. Which explains why nothing changes in the view: the controller still references the old array.
When you change tab, I assume the controller is reinstantiated, the service function is called again, and the controller gets back the new value from the service.
The fix is quite easy: always get the value to display from the service, instead of caching the value in the controller. Replace
$scope.todaysFlavorName = Calendar.getTodaysFlavorName();
by
$scope.todaysFlavorName = function() {
return Calendar.getTodaysFlavorName();
};
and
{{todaysFlavorName[1]}}
by
{{todaysFlavorName()[1]}}

Related

how to retrieve data nested in two collections from firebase with angular

I'm new in Angular - Firebase development, and I am having problems to understand how to retrieve data nested in two collections.
I have a collection named "Orders", which includes a field call "auth", which is the user ID, and I have another collection that is the "User Profile", wich it's $id is the value of "auth". Inside the User Profile I have a field named roomNumber, and it's content I that I want to retrieve every time I read, in ng-repeat of the Orders.
In my view I was trying to do something like this :
<tr ng-repeat="item in items | filter: searchKeyword ">
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
roomNumber is a function in my controller
$scope.roomNumber = function(id) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
});
return childKey
}
When I run this code and see results in my js console, strange things happend:
1. It repeat a lot of times
2. I can never get the childKey value
I have been reading Firebase documentation, but really I do not understand how to do this "silly" thing, does anybody give me a tip of how to do it?
When you bind a function to the $scope and call it within the html it expects to get an answer back right away when called. So when you query firebase and it takes its sweet time getting you back an answer, angularjs has already gotten an answer of undefined from the function.
So what is happening is that you are registering a callback when you provide the function to rootRef.on and then right after you register the callback you are returning the value of childKey. Unfortunately, childKey only gets set by the callback function (which firebase hasn't executed yet). Therefore angularjs gets an answer of undefined from your roomNumber function.
In order to make this work, you are going to have to get the room numbers beforehand and then probably add them to each of your items in $scope.items then use
<td align="left">{{item.$id}} - {{item.room}}</td></tr>
instead of
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
To load all the room numbers you could call some function like this one after $scope.items has loaded
for (var i = 0; i < $scope.items.length; i++) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + $scope.items[i].$id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.val().room;
$scope.items[i].room = childKey;
});
}
It would change each of the items to have a reference to the room. Unfortunately, that list wouldn't update as the data updates, so the better solution would be to do that same query in whatever function was getting your items from the server and add the room to each item as it was being added to the items list.
To fix the issue with childKey not reading you need to use this:
var childKey = rootSnapshot.val().room;
instead of this:
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
Reference: https://www.firebase.com/docs/web/guide/retrieving-data.html

angularjs: controller hijacking another controller's variable

I am facing a very strange issue with variable in one controller being hijacked by another controller. Here are the details:
In my HTML I have two ng-view tags. Each tag leads to a templateURL (an html) that has its own corresponding controller. Ctrl1 and Ctrl2
the two ng-views are at the same level in the html hierarchy - that is, one is NOT the child of another
Controller1 looks like this:
ngEpMod.controller('Ctrl1',[function() {
selfX = this;
abc = 'abc controller1';
console.log(abc); // break point 1
selfX.query = function() {
console.log("abc=");
console.log(abc); // break point 2
console.log("search query=");
console.log(selfX.searchQ);
lySP.searchHomes();
};
}]);
Controller2 looks like this:
ngEpMod.controller('Ctrl2',[function() {
self = this;
abc = 'abc controller2';
}]);
Both controllers are associated in the html using a "controller as" syntax.
The query() method in Ctrl1 is fired when user user clicks a button (ng-click)
Mystery: As I load the html page ($state) that has the two ng-views, I am observing the browser console. I note that abc value at break-point1 is "abc controller1", but when the query() method is fired, it mysteriously changes to "abc controller2". There is no global variable by that name! As I understand, when the page is being laid out, Ctrl1 is created first so at break-point 1 abc has the correct value, then Ctrl2 is created and somehow it high-jacks the abc variable! Stranger even is that I noticed this problem first with my self variable (self = this) and then I introduced abc just for additional check
Gurus, I am a newbie and would really appreciate your help.
By creating a variable without var (or let in ES6), you've created a global variable attached to window:
abc = 'abc controller1'; equals to window.abc = 'abc controller1';
When the 1st controller instantiates it declares the variable abc on window. When the 2nd controller instantiates, it changes the global abc variable content.
To avoid it in this case define var abc in both controllers.
To avoid it in the future add 'use strict'; to each function deceleration, for example:
ngEpMod.controller('Ctrl2',[function() {
'use strict';
self = this;
var abc = 'abc controller2';
}]);
Strict mode will throw error when you make this mistake (any many others). From MDN:
First, strict mode makes it impossible to accidentally create global
variables. In normal JavaScript mistyping a variable in an assignment
creates a new property on the global object and continues to "work"
(although future failure is possible: likely, in modern JavaScript).
Assignments which would accidentally create global variables instead
throw in strict mode:
I would drop the below code into your app above this instantiation (most modern browsers should understand this syntax for debugging). Call window.trackCtrl in every controller constructor and then pop open the console in chrome or firefox and type printCtrls() and you should get a print out of when they were created in order.
window.trackCtrl = (name) => {
var newCtrl = {}
newCtrl.name = name + '_' + performance.now()
window.trackingCtrls = window.trackingCtrls || []
window.trackingCtrls.push(newCtrl)
}
window.printCtrls = () => Array.isArray(window.trackCtrls) ? window.trackingCtrls.forEach(x => console.info(x)) : console.error('trackCtrls not defined')
This will find bugs such as the controllers getting loaded out of order or duplicate copies of your code or libraries getting loaded on the same page. Performance API helps a lot in these situations => https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

How to delete all entries from list?

I am trying to delete all entries from list using angular js. I have two directives I communicate between two directive suing shared service. I am able to add the entry in my list .
I want to delete my list when I click on delete button. I blank my array, but I blank my array on service so it is not reflected on my view. How to blank my list?
Here is my code
https://jsfiddle.net/8fjhLqnw/4/
I delete like that
vm.delete = function() {
vm.data = [];
}
It is not reflecting on my view
For deleting all the entries from a list in Angularjs, you can do something like this
$scope.students.splice( $scope.students.indexOf(student), 1);
Use vm.data.length = 0; to clear the array. If you assign a new array, it will not be bound to the view.
Try this:
vm.delete = function() {
vm.data.length = 0;
}
Because you use f.data = sharedData.data to put the data into the scope of the controller, the will share the same array instance, so modifications on sharedData.data will be seen on f.data, as they are the same.
By sharedData.data = [] you assign a new instance of an array to sharedData.data.
From that point, f.data and sharedData.data are separate instances, any modification done on sharedData.data will leave f.data unaffected.
Another way to workaround this:
.controller('f', function($scope, sharedData) {
this.data = sharedData; // and not this.data = sharedData.data
})
and reference the data as f.data.data. This way you can put in new array instances, because you access it directly through sharedData.

angular - fail to update array from service

I got a service that contain some contacts (name,phone). The controller has array that contain a reference to the array from the service so for every change on the array all gets updated.
Service:
app.service('ContactManagerService', function()
{
this.Contacts=[];
...
this.AddContact=function(Contact){...};
this.RemoveContact=function(Contact){...};
...
});
First question: Is this a good approach? Should every controller/directive need to have a direct reference to the original array from the service? I have read a lot about setting up some events from the service to the controllers when the array has been changed, but it sound stupid because the array on the controller will be change anyway (because its the same array) and the ng-repeat will be updated automatically.
Second problem: The service has a method that replace the array to new one:
this.ReplaceContacts=function(NewContacts)
{
this.Contacts=NewContacts;
});
The ng-repeat does not update because the controller still got the old reference to the old array. So a refresh need to be done.
I tried to replace the code to this one so the same array's reference wont change, but when the the code enter the foreach, this.Contacts array is undefined and the code stops. Why ?!
this.ReplaceContacts=function(NewContacts)
{
this.Contacts.splice(0, this.Contacts.length); //remove all contacts
NewContacts.forEach(function (contact, index)
{
this.Contacts.push(contact);//place the new ones
});
});
The controller code:
app.controller("myCtrl",
function ($scope,ContactManagerService)
{
$scope.Contacts = ContactManagerService.Contacts;
$scope.AddContact= function (Contact1) {
ContactManagerService.AddContact(Contact1);
}
$scope.RemoveContact = function (ContactID) {
ContactManagerService.RemoveContact(ContactID);
}
});
I hope everything is clear,
Thanks.
Because the callback function passed to forEach isn't bound to the service instance. So this, inside the callback, is not the service.
My advice: avoid this like the plague. It's an enormous source of bugs in JavaScript.
Use
var self = this;
at the top of the service, and use self instead of this everywhere.
Or bind the callback function to the service instance:
NewContacts.forEach(function (contact, index) {
...
}, this);
You can simply push elements to Contacts using Array.prototype.push()
The push() method adds one or more elements to the end of an array and returns the new length of the array.
this.ReplaceContacts=function(NewContacts){
this.Contacts.splice(0, this.Contacts.length); //remove all contacts
Array.prototype.push(this.Contacts, NewContacts);
});
As mentioned in previous anser, context of this in forEach loop is not what you think it is.
A simplification would be to use Array.prototype.concat():
var self = this;
self.ReplaceContacts = function (NewContacts) {
self.Contacts.splice(0, this.Contacts.length); //remove all contacts
self.Contacts.concat(NewContacts);
});

adding new property to an object exposed to scope in angularjs

I have an object that is exposed to $scope and has properties attached to it.
I access this object in my controller and call a service which does little something.
What I need to add to it is new property to the object(which is going to be an array attached to the object as a property) so that updated object is returned to the controller and I can access the elements of this array using data-expression tag{{ }}.
I would like to know in detail about making such manipulations the right way and possible ways of doing it.
Just add the array to the object.
$scope.myobj = {};
...
// sometime later
var numbers = [1,2,3];
$scope.myobj.numbers = numbers;
EDIT:
To answer your question about scope in a service. Scope is not accessible in a service. Typically you ask your service for something IE data. But your service can do anything, like add 2 numbers, or in your case create an array of something that you need to attach to your object.
module.service('MyService', function() {
this.add = function(number1, number2) {
return number1 + number2;
}
this.createMyArray = function() {
// pass in whatever you need to this service in order
// to create the array you need.
// example of just returning a hard coded array
return [1,2,3];
}
});
Then you can inject your service into your controller which has the scope you want to modify.
app.controller('MyController', function($scope, MyService) {
$scope.add = function(number1, number2) {
// Lets use our service to add these numbers and
// assign result to scope variable
$scope.answer = MyService.add(number1, number2);
}
$scope.myobj = {};
$scope.makeArray = function() {
// lets use our service again to create an array
// this time lets add it to a scope object that already exists.
$scope.myobj.numbers = MyService.createMyArray();
}
});
A lot of times services are used to grab/update things from a server, so you will see a lot of examples that make http get/post/put/delete calls. Then from your controller(s) you can use those services to grab data. But again you are not limited to that, your service can simple just hold some static data or helper functions too.

Resources