AngularJS: Way to define a large $watchCollection? - javascript

This post is not the same as: Is there a tidy way to define a large watch collection for AngularJS?
My code is (service.js):
var MyJSON = {
array: [
{k:'v'},
{k:'v'},
{k:'v'}
],
info: {
k: 'v',
k2: 'v2'
},
last: 1398680914943 // Date.now()
}
$rootScope.$watchCollection(function () { return MyJSON; }, function (n, o) {
console.log('Change');
});
Detects changes when I work on the root object "MyJSON". Like this:
MyJSON.last = 123456789; // console: Change
But if I am doing something like this:
MyJSON.info.k = 'vDummie';
or:
MyJSON.array.push({k:'something'});
"$watchCollection" does not work.

$watchCollection watches only the 1st level properties; use $watch(..., ..., true) to do "deep" watching. Note: there are 3 arguments, the first two are the same as your code, the third is true for deep watch.

Related

Jasmine test for object properties

What I'd like to do
describe('my object', function() {
it('has these properties', function() {
expect(Object.keys(myObject)).toEqual([
'property1',
'property2',
...
]);
});
});
but of course Object.keys returns an array, which by definition is ordered...I'd prefer to have this test pass regardless of property ordering (which makes sense to me since there is no spec for object key ordering anyway...(at least up to ES5)).
How can I verify my object has all the properties it is supposed to have, while also making sure it isn't missing any properties, without having to worry about listing those properties in the right order?
It's built in now!
describe("jasmine.objectContaining", function() {
var foo;
beforeEach(function() {
foo = {
a: 1,
b: 2,
bar: "baz"
};
});
it("matches objects with the expect key/value pairs", function() {
expect(foo).toEqual(jasmine.objectContaining({
bar: "baz"
}));
expect(foo).not.toEqual(jasmine.objectContaining({
c: 37
}));
});
});
Alternatively, you could use external checks like _.has (which wraps myObject.hasOwnProperty(prop)):
var _ = require('underscore');
describe('my object', function() {
it('has these properties', function() {
var props = [
'property1',
'property2',
...
];
props.forEach(function(prop){
expect(_.has(myObject, prop)).toBeTruthy();
})
});
});
The simplest solution? Sort.
var actual = Object.keys(myObject).sort();
var expected = [
'property1',
'property2',
...
].sort();
expect(actual).toEqual(expected);
it('should contain object keys', () => {
expect(Object.keys(myObject)).toContain('property1');
expect(Object.keys(myObject)).toContain('property2');
expect(Object.keys(myObject)).toContain('...');
});
I ended up here because I was looking for a way to check that an object had a particular subset of properties.
I started with _.has or Object.hasOwnProperties but the output of Expected false to be truthy when it failed wasn't very useful.
Using underscore's intersection gave me a better expected/actual output
var actualProps = Object.keys(myObj); // ["foo", "baz"]
var expectedProps =["foo","bar"];
expect(_.intersection(actualProps, expectedProps)).toEqual(expectedProps);
In which case a failure might look more like
Expected [ 'foo' ] to equal [ 'foo', 'bar' ]
Here are some new possible solutions too:
There's a module for that: https://www.npmjs.com/package/jasmine-object-matchers
With ES2016, you can use a map and convert the object to a map too
I prefer use this; becouse, you have more possibilities to be execute indivual test.
import AuthRoutes from '#/router/auth/Auth.ts';
describe('AuthRoutes', () => {
it('Verify that AuthRoutes be an object', () => {
expect(AuthRoutes instanceof Object).toBe(true);
});
it("Verify that authroutes in key 'comecios' contains expected key", () => {
expect(Object.keys(AuthRoutes.comercios)).toContain("path");
expect(Object.keys(AuthRoutes.comercios)).toContain("component");
expect(Object.keys(AuthRoutes.comercios)).toContain("children");
expect(AuthRoutes.comercios.children instanceof Array).toBe(true);
// Convert the children Array to Object for verify if this contains the spected key
let childrenCommerce = Object.assign({}, AuthRoutes.comercios.children);
expect(Object.keys(childrenCommerce[0])).toContain("path");
expect(Object.keys(childrenCommerce[0])).toContain("name");
expect(Object.keys(childrenCommerce[0])).toContain("component");
expect(Object.keys(childrenCommerce[0])).toContain("meta");
expect(childrenCommerce[0].meta instanceof Object).toBe(true);
expect(Object.keys(childrenCommerce[0].meta)).toContain("Auth");
expect(Object.keys(childrenCommerce[0].meta)).toContain("title");
})
});
I am late to this topic but there is a a method that allows you to check if an object has a property or key/value pair:
expect(myObject).toHaveProperty(key);
expect({"a": 1, "b":2}).toHaveProperty("a");
or
expect(myObject).toHaveProperty(key,value);
expect({"a": 1, "b":2}).toHaveProperty("a", "1");

Data loss in Node.js child process

I'm trying to send data (as an object) to a child process in Node.js, however, all of my regular expressions get lost in transfer.
var arguments = {
something: {
name: 'test',
age: 28,
active; true
},
otherThing: 'some string',
regex: /test/i,
regex2: new RegExp('test')
};
var child = cp.fork(path.join(__dirname, 'child.js'));
child.on('message', function (data) {
console.log(data);
});
child.send(arguments);
In the child.js file I have this at the top:
process.on('message', function () {
console.log(arguments); // This is where the data has changed
});
When the log is output from the child process the arguments object instead looks like this:
{
something: {
name: 'test',
age: 28,
active: true
},
otherThing: 'some string',
regex: {},
regex2: {}
}
So far unable to find anything elsewhere about why this may be happening, any ideas?
Because they are completely separate JavaScript processes, you can't send objects. When you pass an object, it gets serialized to JSON and parsed by the child. (See the docs.)
JSON does not support serializing regex objects. (Try putting JSON.stringify(/abc/) through your console -- you get back "{}".)
To include regexes in a JSON object, you can use the json-fn module. It supports serializing functions, dates, and regexes. (It was actually thanks to an issue i raised that they added regex support. :))
You could then do something like:
var JSONfn = require('json-fn');
var arguments = {
something: {
name: 'test',
age: 28,
active; true
},
otherThing: 'some string',
regex: /test/i,
regex2: new RegExp('test')
};
var child = cp.fork(path.join(__dirname, 'child.js'));
});
child.send(JSONfn.stringify(arguments));
and:
var JSONfn = require('json-fn');
process.on('message', function (data) {
console.log(JSONfn.parse(data))); // This is where the data has changed
});
You can store the regex as a string like
myRegex.string = "/test/";
myRegex.modif = "i";
Send it to child and then use it like
new RegExp(myRegex.string, myRegex.modif);
I tried json-fn but Date objects stringified are not reverted back to Date. This module JSON4Process stringifies the objects' properties of type Date, RegExp, Function, Set and Map while maintaining the object as a javascript object. You don't need to stringify the whole object if you're using fork, you can directly send it.
const { fork } = require('child_process');
const JSON4Process = require('json4process');
let obj = {
date: new Date(),
regex: new RegExp(/regex/g),
func: () => console.log('func')
}
obj = JSON4Process.stringifyProps(obj);
const child = fork('child.js');
child.send(obj);
And then parse the properties back in the other file:
process.on('message', data => {
let obj = JSON4Process.parseProps(data);
});
In case you need to use spawn or exec you can just use the default JSON.stringify over the modified object with json4process:
let newObj = JSON.stringify(JSON4Process.stringifyProps(obj));
let originalObj = JSON4Process.parseProps(JSON.parse(newObj));

Angular unintentional binding/object mirroring

So I am working on a project using AngularJS where I need to be able to compare the values of an object in the scope with it's previously recorded values. I'm doing this through an algorithm such as the one below:
function() {
var data = [
{ id: 1, key: 'value', foo: 'bar'},
{ id: 2, key: 'value', foo: 'bar'}
]
$scope.oldTarget = data[0];
$scope.target = data[0];
}
Now if I were to do:
function() {
$scope.target.foo = 'fighters';
if ($scope.target != $scope.oldTarget) console.log('Target was modified');
console.log($scope.target);
console.log($scope.oldTarget);
}
It will output:
{ id: 1, key: 'value', foo: 'fighters'}
{ id: 1, key: 'value', foo: 'fighters'}
My assumption is that AngularJS is automatically binding the two variables target and oldTarget and mirroring any changes done to target to oldTarget. Is this the case, and if so, is there anyway for me to prevent this? If not, what am I doing that's causing it to do this?
This is not related to Angular, it's default JavaScript behavior. You are referencing the same object. If you intend to modify it without changing the source, you need to clone the object.
Take a look:
What is the most efficient way to clone an object?
Most elegant way to clone a JavaScript object
I assume that this is not angular, this is just how it works, because $scope.oldTarget and $scope.target both is links to the same object.
var test = {foo : 'bar'};
var newTest = test;
newTest.foo = 'changed';
console.log(test);
Th output is: "Object {foo: "changed"}"
http://jsfiddle.net/rf0ac6zf/
Looks like your array element is being referenced "by reference". So create new instances of the element like this:
$scope.oldTarget = $.extend(null,data[0]);
$scope.target = $.extend(null,data[0]);

ko.mapping observableArray always trigger subscription

I am working with ko.mapping plugin in order to map data coming from an ajax request.
Setting the key i expect that subscription is not triggered in this case but it's always raised; i can't understand why. Thx in advance.
var arraySource = [{ Prop: 1, Prop2: 1 }, { Prop: 2, Prop2: 2 }];
var mappedArray = ko.observableArray([]);
mappedArray.subscribe(function (data) {
console.log(data);
});
window.setInterval(function () {
ko.mapping.fromJS(arraySource, {
key: function (data) {
return data.Prop;
}
}, mappedArray);
}, 3000);
Demo: http://jsfiddle.net/xvzAj/
Based on the comment in the docs it sounds like passing the third parameter to .fromJS will overwrite the properties of the array which would trigger the notification.
ko.mapping.fromJS(data, {}, someObject); // overwrites properties on
someObject
Source: http://knockoutjs.com/documentation/plugins-mapping.html
In the knockout.mapping.js ln 627, the array contents are replaced which is triggering the subscription notification.
mappedRootObject(newContents);
https://github.com/SteveSanderson/knockout.mapping/blob/master/build/output/knockout.mapping-latest.debug.js
As #Andrew Walters suggested the subscription will always be triggered, because the entire array is overwritten with the new content.
I found a way to recognoze what really changed by reading the knockout release 3 : http://blog.stevensanderson.com/2013/10/08/knockout-3-0-release-candidate-available/
var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);
myArray.subscribe(function(changes) {
// For this example, we'll just print out the change info
console.log(changes);
}, null, "arrayChange");
inside the subscription it's possible to get the added, deleted and retained elements in a very simple way !

How to observe a change on any property in an array or object?

I want to do something like this
Polymer('bc-datalist', {
listitems: [
{name: 'item1'},
{name: 'item2'}
],
observe: {
'listitems': 'change'
},
change: function () {
// do something
}
});
This doesn't work, so my work around is to do something like this:
Polymer('bc-datalist', {
listitems: {
1:{name: 'item1'},
2:{name: 'item2'}
},
observe: {
'listitems.1.name': 'change',
'listitems.2.name': 'change'
},
change: function () {
// do something
}
});
Is there a way of registering a callback when a object/array item has changed?
Correct me if I'm wrong, but it would appear that there's a typo in your first example. Where you mean to reference listitems as the object to observe, you reference listitem instead. Changing this to listitems would make the case for normal top-level properties work:
<polymer-element name="test-el">
<template>
<button on-click="{{clickHandler}}">Click me</button>
</template>
<script>
Polymer('test-el', {
listitems: [
{name: 'item1'},
{name: 'item2'}
],
observe: {
'listitems': 'change'
},
change: function () {
// do something
alert('listitems changed!');
},
clickHandler: function(){
this.listitems.push({ name: 'item3'});
}
});
</script>
</polymer-element>
Onto your question: Polymer does not call the propertyNameChanged callback for properties included in an observe block to the best of my knowledge. This means you will need to specify the exact nested object path (a.b.c) for what you are trying to observe or manually setup the relevant type of Path/Compound observer manually: https://github.com/Polymer/observe-js
Polymer's observe-js library has support for path observation into an object as well as array observation. The former, can be setup similar to what you have (see 2nd example in the docs).
Polymer('x-element', {
observe: {
'a.b.c': 'validateSubPath'
},
ready: function() {
this.a = {
b: {
c: 'exists'
}
};
},
validateSubPath: function(oldValue, newValue) {
var value = Path.get('a.b.c').getValueFrom(this);
// oldValue == undefined
// newValue == value == this.a.b.c === 'exists'
}
});
I'm checking on ArrayObserver support. Will update this post when I know more.
If you have an array of objects and would like to observe changes made to any of those objects' properties, you can try a custom element, <observe-array-items>. It simplifies the process of creating individual PathObservers for all of the objects in the array, and maintains them as items are added/removed from the list.
In your example, you could do something like
<polymer-element name="test-el">
<template>
<observe-array-items array="{{listitems}}"
path="name"
on-array-item-changed="{{handleNameChanged}}">
</observe-array-items>
</template>
<script>
Polymer({
listitems: [
{name: 'item1'},
{name: 'item2'}
],
handleNameChanged: function (e) {
// This will be called whenever the 'name' property
// of an existing object in listitems changes.
// e.detail.oldValue and e.detail.newValue will
// contain the old and current values
// e.detail.index will correspond to the index of the object in the array.
},
listitemsChanged: function() {
// This will be called whenever an object is added or removed from listitems
}
});
</script>
</polymer-element>

Categories

Resources