Javascript weird nested assignment issue in a object literal - javascript-objects

I have a JS object property defined in an object literal:
reqHeader: [{name:'Chris'},{age:'06'}]
which I am nesting inside another property in the same object literal:
content: {headers:
{reqHeader: this.reqHeader}
},
Now when I try to access this from a method in the same object literal, it says it is undefined:
getHeaders: function(){
var a = this.content['headers']['reqHeader'];
alert(a);
}
Full code: http://jsfiddle.net/Amnesiac/zZP83/5/
Thanks,
Chris.

That won't work, because this is not a reference to that object. That is, it is not the case that JavaScript sets this to refer to an object that's "under construction" inside the object literal block. It remains set to whatever it is outside that expression.
What you can do is something like:
var obj = {
reqHeaders: /* whatever */,
content: {
headers: {
}
}
};
obj.content.headers.reqHeader = obj.reqHeader;

#Pointy is right, this in your case means global object, but not the obj object, if you want to have this referred to your obj, you need to make it an instance of some class/function:
var obj= new (function a(){
this.reqHeader = [{name:'Chris'},{age:'06'}];
this.content = {headers:{reqHeader:this.reqHeader}
};
this.getHeaders = function(){
var a = this.content['headers']['reqHeader'];
alert(a);
};
});
obj.getHeaders();
here is jsfiddle for this

In your JsFiddle you have:
var obj={
reqHeader: [{name:'Chris'},{age:'06'}],
content: {
headers: {
reqHeader:this.reqHeader
}
},
getHeaders: function(){
var a = this.content['headers']['reqHeader'];
alert(a);
}
}
obj.getHeaders();
But when you reference this.reqHeader inside the definition of content.headers.reqHeader, the this variable doesn't point to the main object. Indeed, if you chance that line to this:
content: {
headers: {
reqHeader: 'Hello!'
}
},
it will work, and will alert the word Hello!.

Related

why this resolves to undefined not window/global-env in react component method if it not bind this in constructor

Just described as title.
I got answers that how to fix the situation - this refers to undefined.
But no answer tells me why this refers to undefined.
In my opinion, this will refer to window/global-env if it isn't specified implicitly or explicitly.
Of course ,this will be undefined when in strict-mode.
This is determined at runtime and depending on the code, it can be something different.
this is
determined at runtime, when a function is envoked
determined by how a function is invoked, not where the function is
defined
a reference to an object.
will always be an object
global (this) not available in strict mode
Example 1: this = window
var name = 'Global';
var callName1 = function() {
var name = 'Peter';
console.log('--- From callName1 ----');
console.log(this.name);
//console.log(this);
callName2();
}
var callName2 = function() {
var name = 'Jane';
console.log('--- From callName2 ----');
console.log(this.name);
//console.log(this);
}
callName1();
var execute = function(fn) {
var name = 'Mary';
console.log('--- From execute ----');
console.log(this.name);
//console.log(this);
}
execute(callName2);
Example 2: not available in strict mode
'use strict';
var name = 'Global';
var callName1 = function() {
var name = 'Peter';
console.log('--- From callName1 ----');
console.log(this.name);
console.log(this);
}
callName1();
Example 3: examining this with method invocation
var name = 'global';
var obj = {
name: 'James Obj1',
func: function() {
console.log('--- From func ----');
console.log(this.name);
console.log(this); // this reference obj1
}
}
obj.func()
var obj2 = {
name: 'Jame Obj2',
func: obj.func // this reference obj2, but the function is defined in obj1
}
obj2.func()
var obj3 = {
name: 'Kane Obj3',
obj4: {
name: 'Mary Obj4',
func: function () {
console.log('--- From obj4 ----');
console.log(this.name);
console.log(this); // this reference obj4
}
}
}
obj3.obj4.func()
With () => {} function this - is lexically bound. It means that it uses this from the code that contains the arrow function.
use code below to verify method is in strict mode or not.
Include code below in method.
Got a type error, that means , method is in strict mode, which resolve the confusion.
var obj2 = { get x() { return 17; } };
obj2.x = 5;

Trouble with Controllers to manipulate an Object inside a Service

I have an object which should be accessible in many controllers.
This object is inside a Service, has default values and the controller might change those values later.
My problem is that my object inside the service keep values changed by controllers.
When a controller get the object, I want always that it takes the object with default values. (not with values previously modified by an other controller before...)
I have this inside my service :
this.myObject = {'item1' : 'something', 'item2' : 'other' , .....};
I know that it's not correct because of this.
So I tried to make a method like this :
this.createMyObject = function() {
var obj = myObject;
return obj;
}
And call createMyObject(); in my controllers but this doesn't work too.
I know that the solution might be obvious.
Thanks.
If what you want is a copy of myObject, what you want to do is :
var obj = angular.copy(myObject);
Because var obj = myObject; will just copy the reference of the object, not its content.
Object in Javascript are pass by reference unless copied or cloned. So when you are doing
this.createMyObject = function() {
var obj = myObject;
return obj;
}
The reference of myObject is getting assigned to obj hence, any change in obj will update the myObject as well.
Consider using angular.extend or angular.copy
this.createMyObject = function() {
var obj = {};
angular.copy(myObject, obj);
// or
// obj = angular.copy(myObject);
return obj;
}
Try the below solution:
Service Code:
.service('MyService', function() {
var myObject = {
'item1': '',
'items2': ''
};
/**
* Used to return copy of myObject with some default values
*/
this.createMyObject = function() {
return angular.copy(myObject);
};
this.alterMyObject = function() {
// #TODO here myObject can be used to edit directly
};
});
Note:
"=" operator between two object just used to assign reference of RHS obj to LHS. So any further changes with LHS object will be reflected to RHS obj also.

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

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

Checking if object is empty, works with ng-show but not from controller?

I have a JS object declared like so
$scope.items = {};
I also have a $http request that fills this object with items. I would like to detect if this item is empty, it appears that ng-show supports this... I enter
ng-show="items"
and magically it works,I would also like to do the same from a controller but i can't seem to get it to work, it appears I may have to iterate over the object to see if it has any properties or use lodash or underscore.
Is there an alternative?
I did try
alert($scope.items == true);
but it always returns false , when the object is created and when populated with $http, so its not working that way.
Or you could keep it simple by doing something like this:
alert(angular.equals({}, $scope.items));
In a private project a wrote this filter
angular.module('myApp')
.filter('isEmpty', function () {
var bar;
return function (obj) {
for (bar in obj) {
if (obj.hasOwnProperty(bar)) {
return false;
}
}
return true;
};
});
usage:
<p ng-hide="items | isEmpty">Some Content</p>
testing:
describe('Filter: isEmpty', function () {
// load the filter's module
beforeEach(module('myApp'));
// initialize a new instance of the filter before each test
var isEmpty;
beforeEach(inject(function ($filter) {
isEmpty = $filter('isEmpty');
}));
it('should return the input prefixed with "isEmpty filter:"', function () {
expect(isEmpty({})).toBe(true);
expect(isEmpty({foo: "bar"})).toBe(false);
});
});
regards.
Use an empty object literal isn't necessary here, you can use null or undefined:
$scope.items = null;
In this way, ng-show should keep working, and in your controller you can just do:
if ($scope.items) {
// items have value
} else {
// items is still null
}
And in your $http callbacks, you do the following:
$http.get(..., function(data) {
$scope.items = {
data: data,
// other stuff
};
});
another simple one-liner:
var ob = {};
Object.keys(ob).length // 0
If you couldn't have the items OBJ equal to null, you can do this:
$scope.isEmpty = function (obj) {
for (var i in obj) if (obj.hasOwnProperty(i)) return false;
return true;
};
and in the view you can do:
<div ng-show="isEmpty(items)"></div>
You can do
var ob = {};
Object.keys(ob).length
Only if your browser supports ECMAScript 5. For Example, IE 8 doesn't support this feature.
See http://kangax.github.io/compat-table/es5/ for more infos
if( obj[0] )
a cleaner version of this might be:
if( typeof Object.keys(obj)[0] === 'undefined' )
where the result will be undefined if no object property is set.
Or, if using lo-dash: _.empty(value).
"Checks if value is empty. Arrays, strings, or arguments objects with a length of 0 and objects with no own enumerable properties are considered "empty"."
Check Empty object
$scope.isValid = function(value) {
return !value
}
you can check length of items
ng-show="items.length"

Resources