Why angular scope hold reference? - javascript

JS
angular.module('bindExample', []).controller('ExampleController', ['$scope', function($scope) {
$scope.gridFields = {
id: {
width: 50
},
price: {
width: 60
},
};
$scope.allData = {
'one': {
id: '1234qwe',
price: 900
},
'two': {
id: 'asdadw',
price: 1700
},
'three': {
id: '342sdaw',
price: 1200
},
};
$scope.edit = function(row) {
console.log(row);
$scope.buffer = $scope.allData[row];
}
}]);
HTML
<div ng-app="bindExample">
<div ng-controller="ExampleController">
<table>
<tbody>
<tr ng-repeat="(row, data) in allData">
<td ng-repeat="(field, option) in gridFields" ng-bind="data[field]"></td>
<td><button ng-click="edit(row)">edit</button></td>
</tr>
</tbody>
</table>
<div>
<input type='text' ng-model="buffer.id"/>
</div>
<div>
<input type='text' ng-model="buffer.price"/>
</div>
</div>
</div>
After click on edit, values go to $scope.buffer variable from $scope.allData, and the inputs use the buffer as model, but when input is change the values in allData variable changing as well, but i don't want this, this is why is try to pass the values to other...
Problem illustrated here: JSFIDDLE
Any idea?

Use angular.copy()
$scope.buffer = angular.copy($scope.allData[row]);

Javascript will hold reference if assigned data is either function or object or array.
It provides a great benifit to the developer in many ways . but if you wanna to remove reference you have to clone it.
using angular
$scope.buffer = angular.copy($scope.allData[row]);

First things, you're going to get unexpected results in ng-repeat if you use a parent object literal rather than an array (Angular doesnt guarantee that it will iterate through keys in order):
$scope.allData = [ //you're better off using an Array
'one': {
id: '1234qwe',
price: 900
},
'two': {
id: 'asdadw',
price: 1700
},
'three': {
id: '342sdaw',
price: 1200
},
]; //see above
Secondly, the reason this is happening is that Javascript copies everything as a reference unless it is a primitive, so when you do this:
$scope.buffer = $scope.allData[row];
You're actually just storing a pointer to the original object $scope.allData[row] in $scope.buffer.
To do a "deep copy" yo ucan use angular.copy as suggested by #moncefHassein-bey in his answer.

Related

Add item to array Angular

I have a table with these fields: product, lot, input1, input2. You can clone a line, and you can add a new line.
What I want to do is that for each row you can add a new Lot created by a "number" and by "id" that user write in the input field under the Select lot. And I wanted that the script add the new Lot in the json data and the lot 's option list.
This is the function for add that I tried to do:
$scope.addLot = function() {
var inWhichProduct = row.selectedProduct;
var newArray = {
"number": row.newLot.value,
"id": row.newLot.id
};
for (var i = 0; i < $scope.items.length; i++) {
if ($scope.items[i].selectedProduct === inWhichProduct) {
$scope.items[i].selectedLot.push(newArray);
}
}
};
-->> THIS <<-- is the full code.
Can you help me?
I think your question is a little too broad to answer on Stack Overflow, but here's an attempt:
<div ng-app="myApp" ng-controller="myCtrl">
<table>
<tr ng-repeat="lot in lots">
<td>{{ lot.id }}</td>
<td>{{ lot.name }}</td>
</tr>
</table>
<p>name:</p> <input type="text" ng-model="inputName">
<p>id:</p> <input type="text" ng-model="inputId">
<button ng-click="addLotButton(inputId, inputName)">Add</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.lots = [{
name: "test",
id: 1
},
{
name: "test2",
id: 2
}
];
$scope.addLot = function(lotId, lotName) {
var newLotObject = {
name: lotName,
id: lotId
};
$scope.lots.push(newLotObject);
};
$scope.addLotButton = function(id, name) {
$scope.addLot(id, name);
};
$scope.addLot(3, "Another test");
});
</script>
Basically this code just takes some input and adds an object to the scope for that input. The table is created using an ng-repeat of this data. It's not great code at all but it's just a quick example.
The push method adds newArray to selectedLot array. It's not working on the JSON data but on arrays. If you want to have the JSON, you can give a try to :
var myJsonString = JSON.stringify(yourArray);
It will create a JSON string based on the parameter
Maybe you should try to structure your data to make lots as properties of products.
{
products: [
{id: 1, lots: [{id:1}, {id:2}]},
{id: 2, lots: [{id:1}, {id:2}]}
]
}
To add a lot to a product :
product = products[0];
product.lots.push(newArray);
Change the fallowing:
html:
<button ng-click="addLot(row.selectedProduct.id,row.newLot.value,row.newLot.id)">Add</button>
js:
$scope.addLot = function(id,val,lotId) {
// console.log(id);
var inWhichProduct = id;
var newArray = { "value": val, "id": lotId };
//console.log($scope.items)
angular.forEach($scope.items,function(v,i){
if($scope.items[i].id == id )
{
$scope.items[i].lots.push(newArray);
console.log($scope.items[i].lots);
}
});
};
http://plnkr.co/edit/W8eche8eIEUuDBsRpLse?p=preview

accessing collection inside json object angular

I am new to angular
in the following controller i need to access the object store in my html. But it is not working. Any help
(function () {
'use strict';
angular.module('app').controller('BookController', ['$scope', function ($scope) {
$scope.book = {
id: 1,
name: 'Harry Potter',
author: 'J. K. Rowling',
stores: [
{ id: 1, name: 'Barnes & Noble', quantity: 3 },
{ id: 2, name: 'Waterstones', quantity: 2 },
{ id: 3, name: 'Book Depository', quantity: 5 }
]
};
}]);
});
<div ng-controller="BookController">
{{book.stores}}
</div>
You need to first invoke your anonymous function first using () after the final closing bracket and before the final semi-colon so that the last line looks like this: })();.
You should define angular module first and then amend it with the angular component like controller, service , factory, directive, filters, etc.
angular.module('app', [])
then add ng-app="app" on your page.
Markup
<div ng-app="app" ng-controller="BookController">
{{book.stores}}
</div>
Plunkr Here
Update
If suppose you have multiple store inside the stores object, and you want to show them on the html, then for that you could ng-repeat directive. It will repeat each element on html
<div ng-repeat="s in book.stores">
<span>{{s.name}}</span>
<input type="text" ng-model="s.name" />
<input type="numeric" ng-model="s.quantity" />
</div>
Updated Plunkr

ng-repeat controller and parent update

I've been reading a lot on angular scopes and inheritance but I can't get my head around this problem. Here is the HTML I'm using:
<div class="sensorquery-sensor" ng-repeat="sensor in query.sensors" ng-controller="SensorsCtrl">
<select class="form-control"
ng-model="selected.sensor"
ng-options="sensor.name for sensor in parameters.sensors">
</select>
<select class="form-control"
ng-model="selected.definition"
ng-options="definition.value for definition in definitions">
</select>
<select class="form-control"
ng-model="selected.operation"
ng-options="operation for operation in operations">
</select>
</div>
As you can see, I have an ng-repeat based on query.sensors. The values stored in this query.sensors array should be simple:
{
name: 'sensor1',
type: 'temperature'
}
But I want to use a child controller: SensorsCtrl to handle more logic per sensor and hide the complexitiy of sensors. A sensor can look like:
{
name: 'sensor1',
attributes: [
'model',
'brand'
],
definitions: [
{
datatype: 'double',
value: 'temperature'
},
{
datatype: 'integer',
value: 'pressure'
},
{
datatype: 'string',
value: 'color'
}
]
}
So it's in my SensorsCtrl controller where I want to put the selection logic:
$scope.$watch('selected.sensor', function(sensor) {
$scope.definitions = sensor.template.definition;
});
$scope.$watch('selected.definition', function(definition) {
if (definition.datatype === 'string') {
$scope.operations = ['Count'];
} else {
$scope.operations = ['Max', 'Min'];
}
$scope.selected.operation = _.first($scope.operations);
});
How do I keep the link with the parent query.sensors[$index] while transforming the sensor as the user selects different sensors and definitions?
Setting up a watcher on selected and updating the query.sensors array triggers an infinite $digest loop.
I found the solution which was right before my eyes:
<div class="sensorquery-sensor" ng-repeat="sensor in query.sensors" ng-controller="SensorsCtrl">
<!-- ... -->
</div>
The sensor is a reference to the original object of the parent query.sensors. An it's created in the scope of the sub-controller.
So in my SensorsCtrl controller, I can just watch:
$scope.$watch('sensor.definition', function(definition) {
/* ... */
});
So I can put hide some complexity in this controller while maintaining a proper link to the original element.
It does not answer the question of maintaining a less complex object but it's a different question I guess.

In knockout.js, is it possible to use a dynamic binding value?

In knockout.js, is it possible to let the right-hand-side of a binding (the value of the binding) be dynamic? For example,
<input data-bind="value: dynamicBinding()"/>
<script type="text/javascript">
var vm = {
dynamicBinding : function() {
return "foo().bar";
},
foo : ko.observable({
bar : ko.observable("hi");
}
};
ko.applyBindings(vm);
</script>
the result should be that the the dynamicBinding function is executed while applying the bindings and the resulting string is used as the binding. The input element should be bound to foo().bar, which is the observable with the value "hi".
If you wonder why I would want this, I am trying to render a dynamic table with knockout, where both the rows and the columns are observableArrays, and I want to allow the column definitions to contain the expression of the binding for that column. I.e., I want to be able to do this:
<table data-bind="foreach: data">
<tr data-bind="foreach: $root.columns">
<td data-bind="text: cellValueBinding()"></td>
</tr>
</table>
<script type="text/javascript">
var vm = {
data: ko.mapping.fromJS([
{title: "Brave New World", author: { name : "Aldous Huxley" },
{title: "1984", author: { name : "George Orwell" },
{title: "Pale Fire", author: { name : "Vladimir Nabokov" }]),
columns: ko.observableArray([
{header: "Title", cellValueBinding: function () { return "$parent.title"; }},
{header: "Author", cellValueBinding: function () { return "$parent.author().name"; }}
])
};
ko.applyBindings(vm);
</script>
As you can see from the example, the column definition knows how to extract the value from the data. The table markup itself is more or less a placeholder. But as far as I can tell, this does not work, due to the way knockout processes the bindings. Are there any other options available?
Thanks.
Solution: I ended up using Ilya's suggestion - I can let cellValueBinding be a function that accepts the row and column as arguments, and returns an observable. This technique is demonstrated in this fiddle.
Use ko.computed for it.
Look on example
JSFiddle
EDIT
In your second example, you can pass $parent value ti the function
<td data-bind="text: cellValueBinding($parent)"></td>
and in model
{header: "Title", cellValueBinding: function (parent) { return parent.title; }},
{header: "Author", cellValueBinding: function (parent) { return parent.author().name; }}
JSFiddle

Knockout mapping - Uniquely identifying nested objects using keys

The Knockout mapping plugin documentation has a section entitled "Uniquely identifying objects using “keys”". This describes how to update part of an object and then only update that part of the display rather than completely replacing the display of all properties of a partially-modified object. That all works splendidly in their simple example, which I have slightly modified here to make my question more clear. My modifications were to:
Replace the object with a corrected name after a 2 second delay.
Highlight the unchanging part of the display, so you can see that it is actually not replaced when the update happens.
1. Simple object (jsFiddle)
<h1 data-bind="text: name"></h1>
<ul data-bind="foreach: children">
<li><span class="id" data-bind="text: id"></span> <span data-bind="text: name"></span></li>
</ul>
<script>
var data = {
name: 'Scot',
children: [
{id : 1, name : 'Alicw'}
]
};
var mapping = {
children: {
key: function(data) {
console.log(data);
return ko.utils.unwrapObservable(data.id);
}
}
};
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
var range = document.createRange();
range.selectNode(document.getElementsByClassName("id")[0]);
window.getSelection().addRange(range);
setTimeout(function () {
var data = {
name: 'Scott',
children: [
{id : 1, name : 'Alice'}
]
};
ko.mapping.fromJS(data, viewModel);
}, 2000);
</script>
But what isn't clear to me is how I would achieve the same behavior for a more complex nested data structure. In the following example, I took the above code and wrapped the data in a list. I would like this to behave the same as above, but it doesn't. The whole display is redone because of the change in one property. You can see this because, unlike the above example, the highlighting is lost after the data is updated.
2. More complex nested object (jsFiddle)
<!-- ko foreach: parents -->
<h1 data-bind="text: name"></h1>
<ul data-bind="foreach: children">
<li><span class="id" data-bind="text: id"></span> <span data-bind="text: name"></span></li>
</ul>
<!-- /ko -->
<script>
var data = {
parents: [
{
name: 'Scot',
children: [
{id : 1, name : 'Alicw'}
]
}
]
};
var mapping = {
children: {
key: function(data) {
console.log(data);
return ko.utils.unwrapObservable(data.id);
}
}
};
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
var range = document.createRange();
range.selectNode(document.getElementsByClassName("id")[0]);
window.getSelection().addRange(range);
setTimeout(function () {
var data = {
parents: [
{
name: 'Scott',
children: [
{id : 1, name : 'Alice'}
]
}
]
};
ko.mapping.fromJS(data, viewModel);
}, 2000);
</script>
So basically what I'm asking is, how can I make the second example work like the first, given the more nested data structure? You can assume that ids are unique for each child (so if I added another parent besides Scott, his children would start with id=2, etc.).
Interesting observation there and nice write-up. It appears to work if you define a key on the parent as well as the child. Try this fiddle:
http://jsfiddle.net/8QJe7/6/
It defines instantiable view model functions for the parents and children, where the parent constructor does its child mappings.

Categories

Resources