empty array inside an ko.observable - javascript

I have an ko.observable with an object that contains 3 arrays like this:
self.filter({ file: [], site: [], statut: [] })`
When I try to empty them it doesn't work. I tried
array = []
to empty them. Is it a problem with the observable?

You don't need all of your observable object's arrays to be observable to be able to update the UI, although I'd certainly (like the other answerer) advice you to do so.
I would like to explain however why it doesn't work.
Say you have the following code:
var originalObject = {
myArray: [1, 2, 3]
};
var myObservable = ko.observable(originalObject);
// Resetting the array behind knockout's back:
originalObject.myArray = [1, 2, 3, 4];
The last line changes a property of the object that was used to set the observable. There's no way for knockout to know you've updated your object. If you want knockout to reconsider the observable's vale, you have to tell it something's changed:
myObservable.valueHasMutated();
Now, normally, you update an observable by passing a new or updated variable to it like so:
myObservable(newValue);
Strangely, setting the observable with the same object again also works:
myObservable(originalObject);
This is why:
Internally, knockout compares the newValue to the value it currently holds. If the values are the same, it doesn't do anything. If they're different, it sets the new value and performs the necessary UI updates.
Now, if you're working with just a boolean or number, you'll notice knockout has no problems figuring out if the new value is actually different:
var simpleObservable = ko.observable(true);
simpleObservable.subscribe(function(newValue) {
console.log("Observable changed to: " + newValue);
});
simpleObservable(true); // Doesn't log
simpleObservable(false); // Does log
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
For objects however, it behaves differently:
var myObject = { a: 1 };
var simpleObservable = ko.observable(myObject);
simpleObservable.subscribe(function(newValue) {
console.log("Observable changed to: " + JSON.stringify(newValue, null, 2));
});
simpleObservable(myObject); // Does log, although nothing changed
simpleObservable({b: 2 }); // Does log
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
The subscription is triggered, even though we've used the exact same object to reset our observable! If you dig through knockout's source code, you'll see why. It uses this method to check if the new value is different:
var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
function valuesArePrimitiveAndEqual(a, b) {
var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
return oldValueIsPrimitive ? (a === b) : false;
}
Simply put: if the old value isn't a primitive value, it will just assume things have changed. This means we can update our originalObject, as long as we reset the observable.
originalObject.myArray.length = 0;
myObservable(originalObject);
Or, just as easy:
myObservable(Object.assign(originalObject, { myArray: [] });
A bit of a long answer, but I believe it's nice to know why stuff doesn't work, instead of only circumventing it. Even if simply using observableArrays and letting knockout optimize its work is the better solution!

You mention "emptying" an array. Note that that's different from "assigning a new, empty array to a variable". At any rate, if you want to "empty" an array:
For observableArray check the relevant docs, because they have a removeAll() utility method.
For emptying a plain javascript array, check this duplicate question that has various solutions, one of which is simply array.length = 0.
As a final note, if you're inside the view model, you might need to do self.filter() first to get the object inside the observable. So, for example:
self.filter().file.length = 0; // plain array method
However, since file, site, and statut are plain arrays (and not observableArrays) there will be no automatic updates in your UI. If they were observable arrays, you'd do:
self.filter().file.removeAll(); // assuming `file` has been made observable

Related

What is the difference between these two array assignments in Typescript?

I am working on Angular 4 application.
I found below code in my application but unable to find exact purpose of below code.
getManagementView(groupField: string) {
this.auditList = [...this.auditList.filter(this.filterByRevisit)];
}
I changed it to below code both are working fine.
getManagementView(groupField: string) {
this.auditList = this.auditList.filter(this.filterByRevisit);
}
Could any one help me to understand what is the difference in above two code blocks.
There is noting different. The spread (...) operator destroys the array and gives back the elements one by one and then in the [] put them into the making again an array. Which is actually extra operation.
So this.auditList.filter(this.filterByRevisit) returns an array,
and this [...this.auditList.filter(this.filterByRevisit)] returns an array which is spread and again makes an array.
I don't think there is a difference between the two. ... would create a new array, filter already did it.
However if I take the title:
this.array = this.array // does nothing, same object
this.array = [...this.array] // creates a new array, though the same content

How to change a value in a Reactive Array and rerun the helper

I'm using the Reactive Array package in Meteor and cannot figure out how to change a value in the array and have the helper rerun. This is such a basic thing, I must be missing something obvious but I've not been able to find an answer. Here's my code:
On the client:
test_arr = new ReactiveArray([3]);
Helper:
UI.registerHelper('array_test', function(){
return test_arr.list();
});
In a template:
{{array_test}}
On screen I see '3' as expected, but if I change the value of the reactive array with this:
test_arr[0] = 4
nothing changes on screen, even though if I run test_arr.list() in the console, I see [4]. If I push a new value with:
test_arr.push(5)
then the helper reruns and I see 4,5 on the screen, correctly. So the value had been changed, but the helper did not rerun until I performed an unrelated 'push' operation.
I can't see anything in the docs about updating a value, only adding and removing values.
Is there any way to update a value in a reactive array, reactively?
You can use ReactiveArray.splice() to replace elements in a reactive array, or even create a convenience method that deals with a single element:
ReactiveArray.prototype.setAt = function(pos, item) {
return this.splice(pos, 1, item);
};
arr = new ReactiveArray(['a', 'b', 'c']);
// ["a", "b", "c"]
arr[0];
// "a"
arr.setAt(0, "newVal");
// ["a"]
arr[0];
// "newVal"
Look like this packages doesn't support reactive source when you set value in array by index. Try to check this one package.

AngularJS - binding array value to input ngModel

I have $scope.myArray, and it's binding with an input field by ngModel and the expression {{myArray}}
My issue is when I modified myArray by call changeMyArray(), the input's value did not change. But the expression {{myArray}} is display new value.
So, Why the expression work but input field does not?
I have a way to do, but I want to find a better approach
var newArr = $scope.myArray;
newArr.push("b");
$scope.myArray = angular.copy(newArr);;
Example fiddle
Basically, I think what you want to do is bind the input to a "new entry" scope variable, and then push the value of that variable to your array when the user clicks "Push To". Here's what I mean:
In controller:
$scope.changeMyArray = function() {
$scope.myArray.push($scope.newEntry);
$scope.newEntry = "";
}
In HTML:
<input ng-model="newEntry">
But actually:
Really what you want is a way to edit the contents of an array via text, and have updates to that array from elsewhere also update the text. This is actually pretty simple since browsers come with a JSON library.
I implemented it by starting with a known pair of objects:
$scope.myArray = [];
$scope.myArrayString = "[]";
That way you can update the string via ngModel:
<input ng-model="myArrayString">
Watch for changes on this model to update the actual array:
$scope.$watch("myArrayString", function() {
$scope.myArray = JSON.parse($scope.myArrayString);
});
Then update the string in the changeMyArray function:
$scope.changeMyArray = function() {
$scope.myArray.push("b"); // Or whatever you would like to add here
$scope.myArrayString = JSON.stringify($scope.myArray);
}
Experiment in my fork of the Fiddle.
What's going on?
The variable $scope.myArray is an object, and any object in Javascript can be converted to a string (most complex objects end up as the unhelpful "[object Object]"). Arrays will actually display their contents when converted to a string, so binding an array to HTML via {{myArray}} is pretty straightforward.
However, the reverse conversion is not as simple. In general, a text input can't be bound to an array in a two-way fashion as we'd like. The solution, then, is to use an intermediary variable to hold the string value, and use $scope.$watch to keep the two values in sync.
So you seem to be wondering why when pushing to the array, your $watch function doesn't do the increment. That's because the #watch function only checks object reference equality.
When pushing to the array, the reference stays the same. When you copy the array and set it again in the same variable, the reference changes.
That's why #watchCollection works as expected and increments when each item is pushed.
I have an explanation for my question. Please correct me if I wrong, very thank.
My Question:
Why "myArray" input field does not update when $scope.myArray is changed (Model doesn't update View)?
<input ng-model="myArray" id="myArray">
The answer is AngularJs ng-model doesn't know $scope.myArray is changed. Because ng-model does not perform a deep watch of object (rather than a string or number), it only looks for a change of identity or compares the reference of the objects.
In my case, $scope.myArray is collection. So, although $scope.myArray has changed by push new item (structure is changed), it's reference does not change.
As the result, $setViewValue() and $render() never invoked to update the view.
$render
$setViewValue
Solution:
Sol1: Add new item to $scope.myArray, make a copy of myArray object and then asign a copy to $scope.myArray again. By this way, the object reference is changed. AngularJs see that change and update the view.
var newArr = $scope.myArray;
newArr.push("b");
$scope.myArray = angular.copy(newArr);
Sol2: Create custome $watch('email', function(){...}, true). The last parameter is TRUE to let Angular perform a deep watch. Then, in watch's listener function, I manually set $viewValue = newValue and invoke $render() of ngModelController to update the view. In case we have Formatters, we should invokes them in this step.
$scope.$watch('myArray', function(newValue, oldValue) {
if (newValue !== oldValue) {
var ctrl = angular.element(document.querySelector('#myArray')).controller('ngModel');
// Invoke formatter
var formatters = ctrl.$formatters,
idx = formatters.length;
while(idx--) {
newValue = formatters[idx](newValue);
}
ctrl.$render();
}
}, true);
Please see my script

Assign dynamic array to object

I want to pass following payload to the API
params[field1]: value1
params[field2]: value1
....
params[fieldN]: valueN
I have field and value coming from an object.
var params = {};
jQuery.each($scope.linkParams, function(a, b) {
params.params[a] = b; // returns undefined variable error
// I also tried other options but all result in one or another error
// Some doesn't result into an erro but doesn't get merged. See below merge requirement
});
I also wants to merge the above created object to another object with
jQuery.extend(extraParams, params);
How to achieve the rquired object?
Update
$scope.linkParams = {
field1: 'value1',
field2: 'value2',
....
};
You have two questions, so I'll address them one at a time.
(For a TL;DR, I emboldened the solution text. Hopefully the rest is worth the read, though.)
Object Serialization is Pretty Magical, but Not Quite That Magical
If I had a JS object that I instantiated like the following:
var cat = {
'meow': 'loud',
'type': 'Persian',
'sex': 'male'
}
then it is certainly true that you get attribute reference for free. That is, you can say something like cat.meow, and your runtime environment will make sense of that. However, JS will not automatically create properties of an object that you have referenced do not exist, unless you are referencing them to create them.
cat.health = 'meek' will work, but cat.ears[0] = 'pointy' will not.
var cat = {
'meow': 'loud',
'type': 'Persian',
'sex': 'male'
}
cat.health = 'meek'
alert(cat.health)
cat.ears[0] = 'pointy'
alert(cat.ears[0])
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You'll notice that the first alert happens and contains the expected value, but the second alert never comes. This is because the code fails on the line with cat.ears[0] = 'pointy', and it stops execution at that point.
This may seem to contradict what I just said, but look closely at what's happening. When we attempt to initialize the first element of cat.ears, we must reference the cat.ears property, which does not exist.
JS implementations won't assume that you want to create items up the chain eternally, which is likely by design -- if it didn't throw errors and instead just created any properties or objects that needed to exist in order for your program to by syntactically sound, many pieces of software would silently break when they failed to include required libraries. If you forgot to include JQuery, it'd just create a $, a JQuery variable, and all of the properties of those objects you reference in your code. It'd be a proper mess to debug.
In short, that's what you're -- probably accidentally -- assuming will work here. params.params is analogous to cat.ears in the above example. You may have some reason for doing this, but assuming you don't, your code should function if you simply change params.params[a] to params[a].
JQuery.extend()
Assuming that extraParams is a valid array/object, the code you have written will work once params doesn't break your code anymore, however: do note that this will modify your extraParams object. If you want a new object to contain both params and extraParams, write something more like:
var args = $.extend({}, params, extraParams)
That will modify an empty object and add in the contents of params and extraParams. See the JQuery documentation for more information.
Some manipulations and I was able to achieve the required results.
I am posting the code for further reference:
var d = {};
jQuery.each($scope.linkParams, function(a,b) {
a = "params[" + a + "]";
d[a] = b;
});
jQuery.extend(extraParams, d);

What use does the JavaScript forEach method have (that map can't do)?

The only difference I see in map and foreach is that map is returning an array and forEach is not. However, I don't even understand the last line of the forEach method "func.call(scope, this[i], i, this);". For example, isn't "this" and "scope" referring to same object and isn't this[i] and i referring to the current value in the loop?
I noticed on another post someone said "Use forEach when you want to do something on the basis of each element of the list. You might be adding things to the page, for example. Essentially, it's great for when you want "side effects". I don't know what is meant by side effects.
Array.prototype.map = function(fnc) {
var a = new Array(this.length);
for (var i = 0; i < this.length; i++) {
a[i] = fnc(this[i]);
}
return a;
}
Array.prototype.forEach = function(func, scope) {
scope = scope || this;
for (var i = 0, l = this.length; i < l; i++) {
func.call(scope, this[i], i, this);
}
}
Finally, are there any real uses for these methods in JavaScript (since we aren't updating a database) other than to manipulate numbers like the following?
alert([1,2,3,4].map(function(x){ return x + 1})); // This is the only example I ever see of map in JavaScript.
The essential difference between map and forEach in your example is that forEach operates on the original array elements, whereas map explicitly returns a new array as a result.
With forEach you are taking some action with -- and optionally changing -- each element in the original array. The forEach method runs the function you provide for each element, but returns nothing (undefined). On the other hand, map walks through the array, applies a function to each element, and emits the result as a new array.
The "side effect" with forEach is that the original array is being changed. "No side effect" with map means that, in idiomatic usage, the original array elements are not changed; the new array is a one-to-one mapping of each element in the original array -- the mapping transform being your provided function.
The fact that there's no database involved does not mean that you won't have to operate on data structures, which, after all, is one of the essences of programming in any language. As for your last question, your array can contain not only numbers, but objects, strings, functions, etc.
The main difference between the two methods is conceptual and stylistic: You use forEach when you want to do something to or with each element of an array (doing "with" is what the post you cite meant by "side-effects", I think), whereas you use map when you want to copy and transform each element of an array (without changing the original).
Because both map and forEach call a function on each item in an array, and that function is user-defined, there is almost nothing you can do with one and not with the other. It's possible, though ugly, to use map to modify an array in-place and/or do something with array elements:
var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.map(function(el) {
el.val++; // modify element in-place
alert(el.val); // do something with each element
});
// a now contains [{ val: 2 }, { val: 3 }, { val: 4 }]
but much cleaner and more obvious as to your intent to use forEach:
var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.forEach(function(el) {
el.val++;
alert(el.val);
});
Especially if, as is usually the case in the real world, el is a usefully human-readable variable:
cats.forEach(function(cat) {
cat.meow(); // nicer than cats[x].meow()
});
In the same way, you can easily use forEach to make a new array:
var a = [1,2,3],
b = [];
a.forEach(function(el) {
b.push(el+1);
});
// b is now [2,3,4], a is unchanged
but it's cleaner to use map:
var a = [1,2,3],
b = a.map(function(el) {
return el+1;
});
Note as well that, because map makes a new array, it likely incurs at least some performance/memory hit when all you need is iteration, particularly for large arrays - see http://jsperf.com/map-foreach
As for why you'd want to use these functions, they're helpful any time you need to do array manipulation in JavaScript, which (even if we're just talking about JavaScript in a browser environment) is pretty often, almost any time you're accessing an array that you're not writing down by hand in your code. You might be dealing with an array of DOM elements on the page, or data pulled from an Ajax request, or data entered in a form by the user. One common example I run into is pulling data from an external API, where you might want to use map to transform the data into the format you want and then use forEach to iterate over your new array in order to display it to your user.
The voted answer (from Ken Redler) is misleading.
A side effect in computer science means that a property of a function/method alters a global state [Wikipedia]. In some narrow sense, this may also include reading from a global state, rather than from arguments. In imperative or OO programming, side effects appear most of the time. And you are probably making use of it without realizing.
The significant difference between forEach and map is that map allocates memory and stores the returning value, while forEach throws it away. See the ECMA specification for more information.
As for the reason why people say forEach is used when you want a side effect is that the return value of forEach is always undefined. If it has no side effect (does not change global state), then the function is just wasting CPU time. An optimizing compiler will eliminate this code block and replace the it with the final value (undefined).
By the way, it should be noted that JavaScript has no restriction on side effects. You can still modify the original array inside map.
var a = [1,2,3]; //original
var b = a.map( function(x,i){a[i] = 2*x; return x+1} );
console.log("modified=%j\nnew array=%j",a,b);
// output:
// modified=[2,4,6]
// new array=[2,3,4]
This is a beautiful question with an unexpected answer.
The following is based on the official description of Array.prototype.map().
There is nothing that forEach() can do that map() cannot. That is, map() is a strict super-set of forEach().
Although map() is usually used to create a new array, it may also be used to change the current array. The following example illustrates this:
var a = [0, 1, 2, 3, 4], mapped = null;
mapped = a.map(function (x) { a[x] = x*x*x; return x*x; });
console.log(mapped); // logs [0, 1, 4, 9, 16] As expected, these are squares.
console.log(a); // logs [0, 1, 8, 27, 64] These are cubes of the original array!!
In the above example, a was conveniently set such that a[i] === i for i < a.length. Even so, it demonstrates the power of map(), and in particular its ability to change the array on which it is called.
Note1:
The official description implies that map() may even change length the array on which it is called! However, I cannot see (a good) reason to do this.
Note 2:
While map() map is a super-set of forEach(), forEach() should still be used where one desires the change a given array. This makes your intentions clear.
You can use map as though it were forEach.
It will do more than it has to, however.
scope can be an arbitrary object; it's by no means necessarily this.
As for whether there are real uses for map and forEach, as well to ask if there are real uses for for or while loops.
While all the previous questions are correct, I would definitely make a different distinction. The use of map and forEach can imply intent.
I like to use map when I am simply transforming the existing data in some way (but want to make sure the original data is unchanged).
I like to use forEach when I am modifying the collection in place.
For instance,
var b = [{ val: 1 }, { val: 2 }, { val: 3 }];
var c = b.map(function(el) {
return { val: el.val + 1 }; // modify element in-place
});
console.log(b);
// [{ val: 1 }, { val: 2 }, { val: 3 }]
console.log(c);
// [{ val: 3 }, { val: 4 }, { val: 5 }]
My rule of thumb being making sure when you map you are always creating some new object/value to return for each element of the source list and returning it rather than just performing some operation on each element.
Unless you have any real need to modify the existing list, it doesn't really make sense to modify it in place and fits better into functional/immutable programming styles.
TL;DR answer --
map always returns another array.
forEach does not. It is up to you to decide what it does. Return an array if you want or do something else if you don't.
Flexibility is desirable is certain situations. If it isn't for what you are dealing with then use map.
Others have already posted about your main question regarding the difference between the functions. But for...
are there any real uses for these methods in JavaScript (since we aren't updating a database) other than to manipulate numbers like this:
...it's funny you should ask. Just today I wrote a piece of code that assigns a number of values from a regular expression to multiple variables using map for transformation.
It was used to convert a very complicated text-based structure into visualizable data ... but for simplicity's sake, I shall offer an example using date strings, because those are probably more familiar for everyone (though, if my problem had actually been with dates, instead of map I would've used Date-object, which would've done the job splendidly on its own).
const DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/;
const TEST_STRING = '2016-01-04T03:20:00.000Z';
var [
iYear,
iMonth,
iDay,
iHour,
iMinute,
iSecond,
iMillisecond
] = DATE_REGEXP
// We take our regular expression and...
.exec(TEST_STRING)
// ...execute it against our string (resulting in an array of matches)...
.slice(1)
// ...drop the 0th element from those (which is the "full string match")...
.map(value => parseInt(value, 10));
// ...and map the rest of the values to integers...
// ...which we now have as individual variables at our perusal
console.debug('RESULT =>', iYear, iMonth, iDay, iHour, iMinute, iSecond, iMillisecond);
So ... while this was just an example - and only did a very basic transformation for the data (just for sake of example) ... having done this without map would've been a much more tedious task.
Granted, it is written in a version of JavaScript that I don't think too many browsers support yet (at least fully), but - we're getting there. If I needed to run it in browser, I believe it would transpile nicely.

Categories

Resources