Why is the data in the service not updated from the directive? - javascript

With the following setup I try to update data that is being held in a service and shared with the controller to assign it to the view.
In the example you can see 2 variables. One containing an array, another just a string.
What I don't understand is why is the array updated and consumed in the view and the string is not?!
JavaScript:
function fooService() {
var mystring = 'old string';
var myarray = [];
var updateArray = function(data) {
myarray.push(data);
};
var updateString = function(data) {
mystring = data;
};
return {
myarray: myarray,
mystring: mystring,
updateString: updateString,
updateArray: updateArray
}
}
function MainCtrl($scope, fooService) {
this.myarray = fooService.myarray;
this.mystring = fooService.mystring;
}
function fooDirective(fooService) {
function link(scope) {
fooService.updateArray(scope.vm.name);
fooService.updateString('new string');
}
return {
restrict: 'EA',
replace: true,
template: '<h2 style="color: {{vm.color}};">{{vm.name}}</h2>',
scope: {},
controller: 'MainCtrl',
controllerAs: 'vm',
bindToController: {
name: '#',
color: '#'
},
link: link
};
}
angular
.module('app', [])
.service('fooService', fooService)
.controller('MainCtrl', MainCtrl)
.directive('fooDirective', fooDirective);
HTML:
<div ng-app="app">
<div ng-controller="MainCtrl as vm">
{{vm.myarray}}
{{vm.mystring}}
<foo-directive data-name="Markus" data-color="red"></foo-directive>
<foo-directive data-name="Nemanja" data-color="green"></foo-directive>
<foo-directive data-name="Luke" data-color="blue"></foo-directive>
</div>
</div>
It might just be that I understand it the wrong way but services should hold data that is shared across the app right?
Here is the working example: http://jsfiddle.net/markus_falk/f00y3tL3/6/

services should hold data that is shared across the app right?
This is correct, however when you do
return {
myarray: myarray,
mystring: mystring,
// ...
}
you return new object (that will be your service instance) that has reference to myarray and copy of mystring. So since there is reference to myarray (all objects are passed as a reference) it updates in the service just fine. However, it will not for with the string (primitive types are not-mutable, passed as values), because service returns just a copy of it.
Instead of modifying a string (primitive value) use getter/setter approach.

Related

get rangeError Maximum call stack size exceeded

I have a dropdown with a checkbox that its values is an object. (see attached)
Each time I select an object there is a watch which pastes this new values (which is an array of selected objects) to another object that display it in a directive (binding) -- tiles
$scope.$watch('skills', function(newVal, oldVal) {
if (newVal) {
console.log('changed!!! old value: ' + oldVal + ' new val ' + newVal);
if (newVal === undefined) {
return;
}
$scope.tiles = _.cloneDeep(newVal);
}
}, true);
angular.module('dsadad')
.component('tiles', {
templateUrl: 'tile.tpl.html',
bindings: {
tiles: '<',
onDelete: '&?'
},
controller: function ($state, _, $scope, $log, Utils, $translate, moment) {
'use strict';
var $ctrl = this;
}
});
I get: rangeError Maximum call stack size exceeded
for some reason one of the value of the array of selected objects is an array also instaed of an object as you can see....
Your issue is most likely here $scope.tiles = _.cloneDeep(newVal);
if newVal has a reference to itself, it will recursively try to clone, until you end up with a stackoverflow.
e.g. You probably have a circular object structure like this:
const objectB = {
id: 'B',
otherData: null;
};
const objectA = {
id: 'A',
otherData: objectB;
};
objectB.otherData = objectA;
_.deepClone(objectB) // This will blow up due to the circular reference
Otherwise, newVal is nested so deep that it causes the issue.
E.g.
const objectA = {
a: {
b: {
c: {
// etc. etc.
}
},
}
};
To fix this, you can either remove this circular reference from your data structure, or you can do a shallow copy if it suits your data needs by doing {...newVal}

How to have multiple keys referencing one value in AngularJS

I have the following AngularJS value provider :
myApp.value('myValueProvider',
{
'k1': 'v1',
'k2': 'v2',
'k3': 'v2'
});
Where 'v2' is a complex Object so I don't want to duplicate every v2 values from k2 to k3.
So my goal is to obtain something like this:
myApp.value('myValueProvider',
{
'k1': 'v1',
'k2': 'v2',
'k3': myValueProvider.k2
});
Do you know if it's possible?
How about something like this:
// create the complex object that you want to assign to multiple properties
var complexObj = {
... your complex obj props ...
};
// create the provider object and assign the complex object reference to multiple properties
var valueProviderObj = {
k1: 'v1',
k2: complexObj,
k3: complexObj
};
// associate the provider object to the angular value by key name
myApp.value('myValueProvider', valueProviderObj);
You can change it in run stage.
var myApp = angular.module('myApp', []);
myApp.value('myValueProvider',
{
'k1': 'v1',
'k2': 'v2'
});
myApp.run(function(myValueProvider) {
myValueProvider.k3 = myValueProvider.k2;
});
myApp.controller('ctrl', function(myValueProvider) {
console.log('from controller', myValueProvider);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ctrl"></div>

Setting a new property on an object in an Angular directive for use in the controller

I have a directive that is populating a div. It takes in a object, does some logic to figure out a variable, inserts the variable into the template and returns the template to the div.
How can I set a new property on the object and pass it back to the controller for use?
For example:
HTML:
<div node-icon node="node"></div>
Object going in
{ name: 'Apple',
count: 5,
price: '$5'
}
Directive Logic
.directive('nodeName', [function () {
return {
restrict: 'A',
scope: {node: '='},
template: '<span ng-bind-html="color"></span>',
link: function ($scope) {
if(node.name === 'Apple') {
$scope.color = 'red';
}
}
};
}])
I want to pass the object back to the controller looking like this
{ name: 'Apple',
count: 5,
price: '$5',
color: 'red'
}
If you are sending the object in with scope: {node: '='},
just use scope.node and append the property (it's javascript, it will work).
The link function declaration is wrong , though.
link: function link(scope, element, attrs) { {
if(scope.node.name === 'Apple') {
scope.node.color = 'red';
}
}
Hope it helps
The scope: {node: '='} means there is a two-way binding between the variable of the host scope (the scope in which the directive is used) that is passed in the node attribute, and the node variable in the directive's own scope. Therefore in the link function, you can read and write scope.node and the changes will be reflected in the host scope.
link: function (scope) {
if(scope.node.name === 'Apple') {
scope.node.color = 'red';
}
}

How to create a nested $resource - AngularJS

I'm trying to figure out how to create a nested $resource. I have come up with the following, which seems to work..ish, but seems awfully wrong. Hoping someone can point me in the right direction.
My data structure is:
allData = [{
id:1,
info:'someinfo',
foo :'foobar',
bar : {...},
baz : [{...}, {...}, {...}]
},
{id:2, ...},
{id:3, ...}
];
I would like each object in allData to be a $resource object. I also want each object in the baz array to be a $resource object as well.
What I have come up with is:
var InfoService = angular.module('InfoServices', ['ngResource']);
// The inner resource
InfoService.factory('Baz', ['$resource',
function($resource) {
var baz = $resource('/server/baz', {});
// custom baz methods
baz.prototype.getBaz = function() {};
return baz;
}]);
// Outer resource
InfoService.factory('Info', ['$resource',
function($resource) {
var info = $resource('/server/info', {});
// custom data methods
info.prototype.getInfoStats = function() {};
return info;
}]);
// Array of resources.
InfoService.factory('AllInfo', ['$resource','Info', 'Baz',
function($resource,Info,Baz) {
var resource = $resource('/server/allinfo', {},
{ query : {
method:'get',
isArray:true,
transformResponse:function(data) {
var allinfo = JSON.parse(data);
for (var i=0;i<allinfo.length;i++) {
allinfo[i] = new Info(allinfo[i]);
for (var j=0;j<allinfo[i].baz.length;j++) {
allinfo[i].baz[j] = new Baz(allinfo[i].baz[j]);
}
}
return allinfo;
}
});
return resource;
}]);
Like I said..seems awfully wrong, what's the Angular way to achieving the above?
InfoService.factory('resources', function($resource) {
return {
id: $resource(...),
info: $resource(...),
//...and so on...
}
})
//example usage
InfoService.controller('ctrl', function(resources) {
this.id = resources.id.query();
})
If you want to make everything a resource, this is the way to go. But ask yourself...is it really necessary to make every attribute a separate resource? What about grouping in a resource all attributes that are strictly related? I ask because I am not sure of what these data rapresent to you.

Why Are My Object's Properties Being Overwritten?

I have the following code in Angular, which is an attempt to allow for multiple instances of the same Controller on the same web page. The problem is, every time a new instance is created, it adds itself to the instance object, but erases the other properties (i.e, instances) on the instance object. I think this is a simple javascript syntax problem unrelated to Angular, but I can't figure it out. Any advice?
angular.module('mean.root').controller('ContentA1Controller', ['$scope', '$location', 'Global', 'Data', '$timeout', '$interval', 'Clients', 'Modals', '$element', function ($scope, $location, Global, Data, $timeout, $interval, Clients, Modals, $element) {
// Defaults
$scope.instances = { A: {}, B: {}, C: {}, D: {}, E: {}, F: {} };
// Methods
$scope.createNewInstance = function() {
var instance = $($element).closest('.content-container').attr('id');
// Add ID to content
$($element).find('.content').attr( "id", "contentA1instance"+instance );
Data.priceByDay(function(prices){
// Load Chart Data
$scope.instances[instance].data = [1,2,3,4,5];
// Log Instance Object
console.log($scope.instances);
So, when I add one instance to the controller, it works and I log:
$scope.instances = { A: { data: [1,2,3,4,5] }, B: {}, C: {}, D: {}, E: {}, F: {} }
Then when I add another instance, running the createNewInstance() method again, it will log:
$scope.instances = { A: {}, B: { data: [1,2,3,4,5] }, C: {}, D: {}, E: {}, F: {} }
var instance = $($element).closest('.content-container').attr('id');
....
$scope.instances[instance].data = [1,2,3,4,5];
it's choosing the id of the closest .content-container to the element where the controller is bound in the page. Since you said you've used the controller twice, one of those bindings is closer to .content-container#A and one is closer to .content-container#B. This looks like the code is "working as designed" but you may not have "designed what you meant".
I recommend using a different controller for each instance and keeping shared data in $rootScope, or better yet use one contoller for the whole page and having each instance operate on only one map item.

Categories