Trouble with Controllers to manipulate an Object inside a Service - angularjs

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.

Related

AngularJS is setting value for unrelated scope variable

I have the following angular code to initialize an angular form. It returns a mostly null record except for a couple of dates and employee info.
I was trying to create a scope variable to keep the original record for comparison purposes after the form is filled out. This is what $scope.TechSheetInfoStatic is for.
For our purposes here, I set $scope.TechSheetInfo.Customer.Email to a dummy value. This, while updating $scope.TechSheetInfo, also updates $scope.TechSheetInfoStatic. Why?
$scope.initializeTechSheet = function() {
$scope.TechSheetInfo = [];
$scope.TechSheetInfoStatic = [];
$scope.customerIDDisabled = false;
$scope.orderIDDisabled = false;
const successFunction = function(response) {
$scope.TechSheetInfo = response.data;
$rootScope.customerInfo = response.data.Customer;
$scope.TechSheetInfoStatic = response.data;
$scope.TechSheetInfo.Customer.Email = "bobo#bobo.com";
alert(JSON.stringify($scope.TechSheetInfo.Customer));
alert(JSON.stringify($scope.TechSheetInfoStatic.Customer));
};
const failureFunction = function(response) {
//console.log('Error' + response.status);
};
TechSheetFactory.ITS(successFunction, failureFunction);
};
Use angular.copy to make a deep copy:
const successFunction = function(response) {
$scope.TechSheetInfo = response.data;
$rootScope.customerInfo = response.data.Customer;
̶$̶s̶c̶o̶p̶e̶.̶T̶e̶c̶h̶S̶h̶e̶e̶t̶I̶n̶f̶o̶S̶t̶a̶t̶i̶c̶ ̶=̶ ̶r̶e̶s̶p̶o̶n̶s̶e̶.̶d̶a̶t̶a̶;̶
$scope.TechSheetInfoStatic = angular.copy(response.data);
$scope.TechSheetInfo.Customer.Email = "bobo#bobo.com";
alert(JSON.stringify($scope.TechSheetInfo.Customer));
alert(JSON.stringify($scope.TechSheetInfoStatic.Customer));
};
Since response.data is an object. The assignment statement assigns a reference value to the variable. The angular.copy function will create a new object and copy the contents to the new object.
A variable holding an object does not "directly" hold an object. What it holds is a reference to an object. When you assign that reference from one variable to another, you're making a copy of that reference. Now both variables hold a reference to an object. Modifying the object through that reference changes it for both variables holding a reference to that object.
For more information, see Pass-by-reference JavaScript objects.

Return value from angularjs factory

I try to set up this example https://github.com/AngularClass/angular-websocket#usage
Here is my code
App.factory('MyData', function($websocket, $q) {
var dataStream = $websocket('wss://url');
var collection = [];
dataStream.onMessage(function(message) {
var result = JSON.parse(message.data);
console.log(result);
collection = result;
});
var methods = {
collection: collection,
get: function() {
dataStream.send(JSON.stringify({
api: "volume",
date: "2017-02-01",
interval: 600
}));
}
};
return methods; });
In my controller I wrote:
$interval(function () {
console.log(MyData.collection);
}, 1000);
The problem is that I don't receive any values, however on message arrive I see console log, so websocket itself is obviously alive. If I change collection.push(result) (like in example) I receive constantly growing array. I need only the last value, however. Why collection = result is wrong ?
var collection = []; instantiates a new array and its reference is stored in the variable collection. Then, this reference is assigned to methods.collection and, hence, MyData.collection. However, with JSON.parse a new array is instantiated. collection = result; overwrites the original reference with the reference of the new array. But MyData.collection still holds the reference to original array.
So, there are two ways to encounter the problem:
Don't overwrite the reference to the original array. push is good, but before, you need to clear the array in order to only show the last value.
collection.splice(0, collection.length);
collection.push(result);
However, that would be an array in an array. You probably need to push the values individually (Array.concat will create a new array, too):
collection.splice(0, collection.length);
result.forEach(function(value) {
collection.push(value);
});
Assign the reference of the new array directly to methods.collection. In this case, no extra variable collection is needed.
App.factory('MyData', function($websocket, $q) {
var dataStream = $websocket('wss://url');
var methods = {
collection: [],
get: function() {
dataStream.send(JSON.stringify({
api: "volume",
date: "2017-02-01",
interval: 600
}));
}
};
dataStream.onMessage(function(message) {
var result = JSON.parse(message.data);
console.log(result);
methods.collection = result;
});
return methods;
});

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

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"

Javascript weird nested assignment issue in a object literal

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!.

Resources