Angular JS object scope - angularjs

I have written a simple Angular JS code which greets user depending on the time of the day. It works fine. The code is given below:
var modSvc = angular.module("appName",[]);
modSvc.provider("date",function(){
var greeting;
return {
showGreeting:function(value){
greeting = value;
},
$get:function(){
//it has to return an object which can have variables and functions
return {
showDate:function(){
var date = new Date();
return date.getHours();
},
showGreetingMessage:function(){
//var date = new Date();
return greeting + ", It's "+ this.showDate();
}
}
}
};
});
modSvc.config(function(dateProvider){
var hours = dateProvider.$get().showDate();
if(hours<12)
dateProvider.showGreeting("Good morning!");
else if(hours<17)
dateProvider.showGreeting("Good afternoon!");
else if(hours<22)
dateProvider.showGreeting("Good evening!");
else
dateProvider.showGreeting("Good night!");
});
function serviceController($scope,date){
$scope.greetingMessage = date.showGreetingMessage();
}
modSvc.controller("svcController",serviceController);
If you see showGreetingMessage function it has only one line of code which is:
return greeting + ", It's "+ this.showDate();
However showDate function resides at the same level as that of showGreetingMessage function. That's why this.showDate should not work and it should give error. But it works perfectly fine. How is it so?

It depends on the JavaScript engine that is used but according to the Mozilla reference your "this" actually references the parent object since you are returning an object.
As an object method
When a function is called as a method of an object, its this is set to the object the method is called on.
In the following example, when o.f() is invoked, inside the function this is bound to the o object.
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // logs 37

Related

ng-if only works when referencing var direct from service, instead of var in controller scope

I am trying to understand why my ng-if statement doesn't work when I reference a local variable in my controller that is assigned to a value from a service, but it works properly if assigned directly to the value from that service.
For example, this works:
<div class="map" ng-if="interactiveMap.mapService.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
with the following controller:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.mapService = map;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
But if I were to assign the "esriLoaded" var to a local var in the scope, like this:
<div class="map" ng-if="interactiveMap.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
Controller here:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.esriLoaded = map.esriLoaded;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
Then it doesn't work. The value for "esriLoaded" is always false (which is the default value for esriLoaded). It's like it isn't updating the value of self.ersiLoaded when the value gets updated in the "map" service. Here is the code for the "map" service, just in case folks need it to answer this question.
angular.module('tamcApp')
.service('map', function (config, esriLoader, esriRegistry, esriMapUtils) {
// AngularJS will instantiate a singleton by calling "new" on this function
var self = this;
self.esriLoaded = false;
self.lazyload = function() {
// Make a call to load Esri JSAPI resources.
// A promise is provided for when the resources have finished loading.
esriLoader.bootstrap({
url: config.globals.esriJS
}).then(function() {
// Set Loaded to be true
self.esriLoaded = true;
// DEFINE CUSTOM BASEMAP USED BY ALL MAPS
esriMapUtils.addCustomBasemap('mcgiStreet', {
urls: ['http://myhost.com/arcgis/rest/services/BaseMap/StreetMap/MapServer'],
title: 'MCGI Street Map',
thumbnailurl: ''
});
});
};
if (!self.esriLoaded) {
self.lazyload();
}
self.getMap = function(id){
return esriRegistry.get(id);
};
});
That is actually not because of angular, but because of JavaScript. map.esriLoaded is a boolean value, a primitive and thus not an object, which leads to your local self.esriLoaded not becoming a reference (as only objects can be referenced), but just a plain copy of the boolean value contained in map.esriLoaded.
A short example to make it more clear:
//Primitive
var a = 5; //primitive
var b = a; //b just copies the value of a
a = 6; //This will change a, but not b
conosle.log(b); //will print 5
//Object
var a = { someValue: 5 }; //a is now a reference to that object
var b = a; //b also becomes a reference to the object above
a.someValue = 1337; //will change the object a is referencing, thus also
//changing the object b is referencing, as its the same object
console.log(b.someValue); //will print 1337

getAttribute return unresolved

I'm just trying to save the attribute of an input value to a variable.
This is the code:
var sliderNumber = element.all(by.model('color.red')).get(1);
var firstNum = sliderNumber.getAttribute('value').then(function(value) {
return value;
});
//Some code that changes the sliderNumber attribute
expect(sliderNumber.getAttribute('value')).toEqual(firstNum + 1);
This gives me an error like this:
Expected '184' to equal Promise::433 ([[PromiseStatus]]: "pending")1.
I've also tried:
var firstNum = function() {
return sliderNumber.getAttribute('value').then(function(value) {
return value;
});
}
That didn't help at all. How do I resolve this promise?
Since .getAttribute() returns a promise, which will invoke your callback asynchronously, you need to put your test logic within the callback:
var sliderNumber = element.all(by.model('color.red')).get(1);
// get the initial value
sliderNumber.getAttribute('value').then(function(value) {
// once we've got the initial value, store it, then proceed with your test
var initialValue = parseInt(value, 10);
//Some code that changes the sliderNumber attribute
expect(sliderNumber.getAttribute('value')).toEqual((initialValue + 1).toString());
});
You can't simply get the return value outside of the callback, because that callback may or may not be called after the rest of your code.

Angular - Organise controller, factory and "class"

I would like to understand how to have a nice organisation in my angular project.
[see code below]
Does it makes sense to have the getFireList function into the Factory ? Or should i put it into the controller ?
Does the "class" Fire makes sense ? Should i remove it ? Should i move it to the controller ? Should i move it the the factory ?
If you see anything wrong in this code i'm really interested to learn more.
For now, i've got this :
A class "Fire" to create new object of type Fire.
function Fire (p_power) {
// ATTRIBUTES
this.id = null;
this.power = p_power;
this.position = {
x: null,
y: null
}
// GETTERS/SETTERS
// id
this.getId = function() {
return this.id;
}
this.setId = function(p_id) {
this.id = p_id;
}
// power
this.getPower = function() {
return this.power;
}
this.setPower = function(p_power) {
this.power = p_power;
}
// position
this.getPosition = function() {
return this.position;
}
this.setPosition = function(p_position) {
this.position = p_position;
}
// METHODS
this.increasePower = function(p_plus) {
this.power += p_plus;
}
this.decreasePower = function(p_minus) {
this.power -= p_minus;
}
}
A controller
simuApp.controller('FireController', function($scope, FireFactory) {
// ...
});
And a factory
simuApp.factory('FireFactory', function() {
return {
fire_list: [],
getFireList : function() {
return $http.get(site_url+'fire/fireList').
then(
function(success) {
var data = success.data;
var fires = [];
var fire_tmp;
for (i=0 ; i<data.length ; i++) {
fire_tmp = new Fire( data[i].power );
fire_tmp.setId( data[i].idFire );
fires.push( fire_tmp );
}
fire_list = fires;
return fire_list;
}, function(err) {
// ...
}
);
}
}
});
Thanks for your help.
First, let's get the terminology right. .factory is a method to register a function that generates an instance of the service - hence "factory". What it generates, though, is a singleton service instance.
So, the service you create would be more properly named as FireSvc (as opposed to FireFactory), whereas the function that creates it could have the word "factory" in it (although, in the case below, that function name is not really needed - it could just be an anonymous function):
.factory("FireSvc", function FireSvcFactory(){
});
It is a good practice to use a Service to abstract away any domain/business logic from the controller. Keep the controller thin, responsible only to define the ViewModel, and react to events by changing the ViewModel or invoking functions on the Model.
So, having FireSvc.getFireList() makes sense.
Now, whether the list is a collection of plain objects, or instances of Fire is completely independent of Angular and is entirely up to you. In any case, it is too broad of a topic to discuss in a SO answer.

angularfire: Having trouble getting a Firebase array from my factory with $asArray()

I've got a factory that gets my data from Firebase, and I want my controller to be able to access it. However, when I console.log the data in my controller, it isn't the Array[10] that I would expect it to be, but rather an Array with keys 0,1,2,..10, $$added, $$error, $$moved,... and so on. However, when I skip out on using the factory, and use $asArray() method on my firebase ref directly in my controller it shows up nicely as an Array[10]
In my factory, this is what it looks like..
var listingsref = new Firebase("https://something.firebaseio.com");
var sync2 = $firebase(listingsref);
var products = sync2.$asArray();
factory.getProducts = function(){
return products;
};
Controller
$scope.products = marketFactory.getProducts();
console.log($scope.products) in my controller should be Array[10], but instead it's an Array with the data + a lot more $$ methods. Anyone know what's going on? Thanks
EDIT: Full Factory File
(function(){
var marketFactory = function($firebase){
var listingsref = new Firebase("https://something.firebaseio.com");
var sync2 = $firebase(listingsref);
var products = sync2.$asArray();
var factory = {};
factory.getProducts = function(){
console.log(products);
return products;
};
factory.getProduct = function(productId){
for(var x = 0; x<products.length ;x++){
if(productId == products[x].id){
return {
product:products[x],
dataPlace:x
};
}
}
return {};
};
factory.getNextProduct = function(productId, e){
var currentProductPlace = factory.getProduct(productId).dataPlace;
if (e=="next" && currentProductPlace<products.length){
return products[currentProductPlace+1];
}
else if(e=="prev" && currentProductPlace>0){
return products[currentProductPlace-1];
}
else{
return {};
}
};
factory.componentToHex = function(c){
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
};
factory.rgbToHex = function(r,g,b){
return "#" + factory.componentToHex(r) + factory.componentToHex(g) + factory.componentToHex(b);
};
factory.hexToRgb = function(hex) {
if(hex.charAt(0)==="#"){
hex = hex.substr(1);
}
var bigint = parseInt(hex, 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return r + ", " + g + ", " + b;
};
factory.parseRgb = function(rgb){
rgb = rgb.replace(/\s/g, '');
var red = parseInt(rgb.split(',')[0]);
var green = parseInt(rgb.split(',')[1]);
var blue = parseInt(rgb.split(',')[2]);
return {
r:red,
g:green,
b:blue
};
};
return factory;
};
marketFactory.$inject = ['$firebase'];
angular.module('marketApp').factory('marketFactory', marketFactory);
}());
This snippet gets a synchronized AngulareFire array of products:
var products = sync2.$asArray();
The AngularFire documentation is a bit off on this point: what you get back from $asArray() is not an array, but the promise of an array. At some point in the future your products variable will contain an array. This is done because it may take (quite) some time for your array data to be downloaded from Firebase. Instead of blocking your code/browser while the data is downloading, it returns a wrapper object (called a promise) and just continues.
Such a promise is good enough for AngularJS: if you simply bind products to the scope and ng-repeat over it, your view will show all products just fine. This is because AngularFire behind the scenes lets AngularJS know when the data is available and Angular then redraws the view.
But you said:
console.log($scope.products) in my controller should be Array[10]
That is where you're mistaken. While AngularFire ensures that its $asArray() promise works fine with AngularJS, it doesn't do the same for console.log. So your console.log code runs before the data has been downloaded from Firebase.
If you really must log the products, you should wait until the promise is resolved. You this this with the following construct:
products.$loaded().then(function(products) {
console.log(products);
});
When you code it like this snippet, the data for your products will have been downloaded by the time console.log runs.
Note that the object will still have extra helper methods on it, such as $add. That is normal and also valid on an array. See the documentation for FirebaseArray for more information on what the methods are, what they're for an how to use them.
So I edited the code in the plnkr at http://plnkr.co/M4PqojtgRhDqU475NoRY.
The main differences are the following:
// Add $FirebaseArray so we can extend the factory
var marketFactory = function($firebase, $FirebaseArray){
var listingsref = new Firebase("https://something.firebaseio.com");
// Actually extend the AngularFire factory and return the array
var MarketFactory = $FirebaseArray.$extendFactory(factory);
return function() {
var sync = $firebase(listingsref, {arrayFactory: factory});
return sync.$asArray();
};
Check out https://www.firebase.com/docs/web/libraries/angular/guide.html#section-extending-factories for more information on extending AngularFire entries. You will likely need to make some adjustments to the rest of the factory code.

How to extend returned objects in the list returned by $asArray?

I'm having trouble decorate the objects in my list returned by $asArray in angularfire with a new method (not decorating the array itself).
The angularfire documentation seems to suggest that the right way to do this is to override the $$added method in the factory for $FirebaseArray, returning a new object that either encapsulates or extends the snapshot that gets passed in to that method. From the documentation:
// an object to return in our JokeFactory
app.factory("Joke", function($firebaseUtils) {
function Joke(snapshot) {
this.$id = snapshot.name();
this.update(snapshot);
}
Joke.prototype = {
update: function(snapshot) {
// apply changes to this.data instead of directly on `this`
this.data = snapshot.val();
},
makeJoke: function() {
alert("Why did the " + this.animal + " cross the " + this.obstacle + "?");
},
toJSON: function() {
// since we didn't store our data directly on `this`, we need to return
// it in parsed format. We can use the util function to remove $ variables
// and get it ready to ship
return $firebaseUtils.toJSON(this.data);
}
};
return Joke;
});
app.factory("JokeFactory", function($FirebaseArray, Joke) {
return $FirebaseArray.$extendFactory({
// change the added behavior to return Joke objects
$$added: function(snap) {
return new Joke(snap);
},
// override the update behavior to call Joke.update()
$$updated: function(snap) {
this.$getRecord(snap.name()).update(snap);
}
});
});
However, when I do this in my code, nothing ever gets added to the array, although I can see from outputting to the console that it is getting called.
var printMessageObjConstructor = function(snap) {
this.$id = snap.name();
this.snapshot = snap;
this.$update = function(snap) {
this.snapshot = snap;
};
this.printMessage = function() {
return this.author + "'s question is: " + this.body;
};
};
var ref = new Firebase("https://danculley-test.firebaseio.com/questions");
//What Am I Doing Wrong Here?
var arrayFactory = $FirebaseArray.$extendFactory({
$$added: function(snap, prevChild) {
var x = new printMessageObjConstructor(snap);
console.log("I am being called from FirebaseDecoratedCtlOverloadAddedinNewObj.");
return x;
},
$createObject: function(snap) {
return new printMessageObjConstructor(snap);
},
$$updated: function(snap) {
var i = this.$indexFor(snap.name());
var q = this.$list[i];
q.$update(snap);
}
});
var sync = $firebase(ref, {arrayFactory:arrayFactory});
var list = sync.$asArray();
list.$loaded(function(list) {
$scope.questions = list;
});
I've set up a new plunk stripped down to show the issue with a couple other use cases that I've tried. (The actual method I'm adding is more complex and isn't related to the view, but I wanted to do something simple to reproduce the issue.)
I think the issue is that I don't quite understand what exactly $$added is supposed to return, or what additional behavior beside returning the value to be stored $$added is supposed to have. There also doesn't really seem to be an $$added on the prototype or on $FirebaseArray to call as a super to get the default behavior. Can someone point me in the right direction?
UPDATE
For the benefit of others, after reviewing the like that Kato posted, I was able to solve the issue by adding the following, almost all copied directly from the source except for the commented line below.
$$added: function(snap, prevChild) {
var i = this.$indexFor(snap.name());
if( i === -1 ) {
var rec = snap.val();
if( !angular.isObject(rec) ) {
rec = { $value: rec };
}
rec.$id = snap.name();
rec.$priority = snap.getPriority();
$firebaseUtils.applyDefaults(rec, this.$$defaults);
//This is the line that I added to what I copied from the source
angular.extend(rec, printMessageObj);
this._process('child_added', rec, prevChild);
}
}
For the benefit of others, after reviewing the link that Kato posted, I was able to solve the issue by adding the following, almost all copied directly from the source except for the commented line below.
$$added: function(snap, prevChild) {
var i = this.$indexFor(snap.name());
if( i === -1 ) {
var rec = snap.val();
if( !angular.isObject(rec) ) {
rec = { $value: rec };
}
rec.$id = snap.name();
rec.$priority = snap.getPriority();
$firebaseUtils.applyDefaults(rec, this.$$defaults);
//This is the line that I added to what I copied from the source
angular.extend(rec, printMessageObj);
this._process('child_added', rec, prevChild);
}
}

Resources