Equivalent of angular.equals in angular2 - angularjs

I am working on migration of angular 1 project to angular 2 . In angular 1 project I was using angular.equals for object comparison angular.equals($ctrl.obj1, $ctrl.newObj); , I searched online for equivalent method in angular 2 but could not find any matching result.

#Günter Yes you are right there is no equivalent in angular2 . While searching more I found third party library lodash which will do same job as angular.equals and syntax is same as angular one and this library solves my problem
Code example from lodash documentation
var object = { 'a': 1 };
var other = { 'a': 1 };
_.isEqual(object, other);
// => true
object === other;
// => false

I rewrote Ariels answer (thank you!) to be TSLINT-friendly. You can also save some continues by using else if, but I think this is more clear. Maybe someone else needs it too:
export function deepEquals(x, y) {
if (x === y) {
return true; // if both x and y are null or undefined and exactly the same
} else if (!(x instanceof Object) || !(y instanceof Object)) {
return false; // if they are not strictly equal, they both need to be Objects
} else if (x.constructor !== y.constructor) {
// they must have the exact same prototype chain, the closest we can do is
// test their constructor.
return false;
} else {
for (const p in x) {
if (!x.hasOwnProperty(p)) {
continue; // other properties were tested using x.constructor === y.constructor
}
if (!y.hasOwnProperty(p)) {
return false; // allows to compare x[ p ] and y[ p ] when set to undefined
}
if (x[p] === y[p]) {
continue; // if they have the same strict value or identity then they are equal
}
if (typeof (x[p]) !== 'object') {
return false; // Numbers, Strings, Functions, Booleans must be strictly equal
}
if (!deepEquals(x[p], y[p])) {
return false;
}
}
for (const p in y) {
if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
return false;
}
}
return true;
}
}

Instead of writing a function to iterate through the objects, you could just use JSON.stringify and compare the two strings?
Example:
var obj1 = {
title: 'title1',
tags: []
}
var obj2 = {
title: 'title1',
tags: ['r']
}
console.log(JSON.stringify(obj1));
console.log(JSON.stringify(obj2));
console.log(JSON.stringify(obj1) === JSON.stringify(obj2));

In Angular 2 you should use pure JavaScript/TypeScript for that so you can add this method to some service
private static equals(x, y) {
if (x === y)
return true;
// if both x and y are null or undefined and exactly the same
if (!(x instanceof Object) || !(y instanceof Object))
return false;
// if they are not strictly equal, they both need to be Objects
if (x.constructor !== y.constructor)
return false;
// they must have the exact same prototype chain, the closest we can do is
// test there constructor.
let p;
for (p in x) {
if (!x.hasOwnProperty(p))
continue;
// other properties were tested using x.constructor === y.constructor
if (!y.hasOwnProperty(p))
return false;
// allows to compare x[ p ] and y[ p ] when set to undefined
if (x[p] === y[p])
continue;
// if they have the same strict value or identity then they are equal
if (typeof (x[p]) !== "object")
return false;
// Numbers, Strings, Functions, Booleans must be strictly equal
if (!RXBox.equals(x[p], y[p]))
return false;
}
for (p in y) {
if (y.hasOwnProperty(p) && !x.hasOwnProperty(p))
return false;
}
return true;
}

You could just copy the original source code from angularjs for the angular.equals function. Usage: equals(obj1, obj2);
var toString = Object.prototype.toString;
function isDefined(value) {return typeof value !== 'undefined';}
function isFunction(value) {return typeof value === 'function';}
function createMap() {
return Object.create(null);
}
function isWindow(obj) {
return obj && obj.window === obj;
}
function isScope(obj) {
return obj && obj.$evalAsync && obj.$watch;
}
function isRegExp(value) {
return toString.call(value) === '[object RegExp]';
}
function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }
function isDate(value) {
return toString.call(value) === '[object Date]';
}
function isArray(arr) {
return Array.isArray(arr) || arr instanceof Array;
}
function equals(o1, o2) {
if (o1 === o2) return true;
if (o1 === null || o2 === null) return false;
// eslint-disable-next-line no-self-compare
if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
if (t1 === t2 && t1 === 'object') {
if (isArray(o1)) {
if (!isArray(o2)) return false;
if ((length = o1.length) === o2.length) {
for (key = 0; key < length; key++) {
if (!equals(o1[key], o2[key])) return false;
}
return true;
}
} else if (isDate(o1)) {
if (!isDate(o2)) return false;
return simpleCompare(o1.getTime(), o2.getTime());
} else if (isRegExp(o1)) {
if (!isRegExp(o2)) return false;
return o1.toString() === o2.toString();
} else {
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
keySet = createMap();
for (key in o1) {
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
if (!equals(o1[key], o2[key])) return false;
keySet[key] = true;
}
for (key in o2) {
if (!(key in keySet) &&
key.charAt(0) !== '$' &&
isDefined(o2[key]) &&
!isFunction(o2[key])) return false;
}
return true;
}
}
return false;
}

a = { name: 'me' }
b = { name: 'me' }
a == b // false
a === b // false
JSON.stringify(a) == JSON.stringify(b) // true
JSON.stringify(a) === JSON.stringify(b) // true

Related

Sort the array with specific character in swift iOS [duplicate]

Given is the following:
var theArray: [String] = ["uncool", "chill", "nifty", "precooled", "dandy", "cool"]
I want to sort the array by how similar the words are to the key word.
var keyWord: String = "cool"
The wanted result would be:
print// ["cool", "uncool", "precooled", ...] and then it does not matter anymore. But the words that are the key or contain it should be the very first objects.
My closest tryout so far has been:
let _theArray = entries.sorted { element1, element2 in
return element1.contains(keyWord) && !element2.contains(keyWord)
}
But that results in uncool being the first item, then precooled and the most related item cool even comes after nifty .
What am I missing?
You can define your own similarity sorting method. Note that I have also added a hasPrefix priority over the ones which only contains the keyword which you can just remove if you don't want it:
var theArray = ["chill", "nifty", "precooled", "cooldaddy", "cool", "coolguy", "dandy", "uncool"]
let key = "cool"
let sorted = theArray.sorted {
if $0 == key && $1 != key {
return true
}
else if $0.hasPrefix(key) && !$1.hasPrefix(key) {
return true
}
else if !$0.hasPrefix(key) && $1.hasPrefix(key) {
return false
}
else if $0.hasPrefix(key) && $1.hasPrefix(key)
&& $0.count < $1.count {
return true
}
else if $0.contains(key) && !$1.contains(key) {
return true
}
else if !$0.contains(key) && $1.contains(key) {
return false
}
else if $0.contains(key) && $1.contains(key)
&& $0.count < $1.count {
return true
}
return false
}
print(sorted) // ["cool", "coolguy", "cooldaddy", "uncool", "precooled", "chill", "nifty", "dandy"]
You can also extend Sequence and create a sorted by key similarity method:
extension Sequence where Element: StringProtocol {
func sorted<S>(by key: S) -> [Element] where S: StringProtocol {
sorted {
if $0 == key && $1 != key {
return true
}
else if $0.hasPrefix(key) && !$1.hasPrefix(key) {
return true
}
else if !$0.hasPrefix(key) && $1.hasPrefix(key) {
return false
}
else if $0.hasPrefix(key) && $1.hasPrefix(key)
&& $0.count < $1.count {
return true
}
else if $0.contains(key) && !$1.contains(key) {
return true
}
else if !$0.contains(key) && $1.contains(key) {
return false
}
else if $0.contains(key) && $1.contains(key)
&& $0.count < $1.count {
return true
}
return false
}
}
}
let sorted = theArray.sorted(by: key) // "cool", "coolguy", "cooldaddy", "uncool", "precooled", "chill", "nifty", "dandy"]
And the mutating version as well:
extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
mutating func sort<S>(by key: S) where S: StringProtocol {
sort {
if $0 == key && $1 != key {
return true
}
else if $0.hasPrefix(key) && !$1.hasPrefix(key) {
return true
}
else if !$0.hasPrefix(key) && $1.hasPrefix(key) {
return false
}
else if $0.hasPrefix(key) && $1.hasPrefix(key)
&& $0.count < $1.count {
return true
}
else if $0.contains(key) && !$1.contains(key) {
return true
}
else if !$0.contains(key) && $1.contains(key) {
return false
}
else if $0.contains(key) && $1.contains(key)
&& $0.count < $1.count {
return true
}
return false
}
}
}
First you need a measure of how similar two strings are. Here's a simple example:
extension String {
func equalityScore(with string: String) -> Double {
if self == string {
return 2 // the greatest equality score this method can give
} else if self.contains(string) {
return 1 + 1 / Double(self.count - string.count) // contains our term, so the score will be between 1 and 2, depending on number of letters.
} else {
// you could of course have other criteria, like string.contains(self)
return 1 / Double(abs(self.count - string.count))
}
}
}
Once you have that, you can use it to sort the array:
var theArray: [String] = ["uncool", "chill", "nifty", "precooled", "dandy", "cool"]
var compareString = "cool"
theArray.sort { lhs, rhs in
return lhs.equalityScore(with: compareString) > rhs.equalityScore(with: compareString)
}
Result: ["cool", "uncool", "precooled", "chill", "nifty", "dandy"]

I cannot set while to work properly in for loops in ts

I want to get array of arrays with unique values, but at some point
while
gets missed or ignored (I presume because of the asynchronous nature of it). Can someone help with a adding a promise to this, or setting
async/await
structure or giving better advise how to check the arrays. I tried adding
async/await
but I get error and I am not sure where I can add a promise or use it. It is
getSeveralArrays() {
for (let index = 0; index < 50; index++) {
this.getValue();
}
}
getValue() {
this.i++;
this.array = [];
this.randomArray = [];
for (let index = 0; index < 4; index++) {
this.randomValue = this.getRandom();
if (this.array.length === 2) {
while ((this.array[0] === this.randomValue) || (this.array[1] === this.randomValue)) {
this.randomValue = this.getRandom();
}
this.array.push(this.randomValue);
} else if (this.array.length === 3) {
while ((this.array[0] === this.randomValue) || (this.array[1] === this.randomValue) || (this.array[2] === this.randomValue)) {
this.randomValue = this.getRandom();
}
this.array.push(this.randomValue);
} else {
this.array.push(this.randomValue);
}
console.log({...this.array});
this.randomArray.push({ind: this.i, val: this.array});
}
}
getRandom() {
const value = Math.floor(Math.random() * 4);
return value;
}
There is nothing asynchronous in all your code, so no need for async/await. Also, while is not asynchronous.
You were missing a case for the length of 1 though, so the second element could always be the same as the first.
class X {
getSeveralArrays() {
for (let index = 0; index < 50; index++) {
this.getValue();
}
}
getValue() {
this.i++;
this.array = [];
this.randomArray = [];
for (let index = 0; index < 4; index++) {
this.randomValue = this.getRandom();
if (this.array.length === 1) {
while (this.array[0] === this.randomValue) {
this.randomValue = this.getRandom();
}
this.array.push(this.randomValue);
} else if (this.array.length === 2) {
while (this.array[0] === this.randomValue || this.array[1] === this.randomValue) {
this.randomValue = this.getRandom();
}
this.array.push(this.randomValue);
} else if (this.array.length === 3) {
while (
this.array[0] === this.randomValue ||
this.array[1] === this.randomValue ||
this.array[2] === this.randomValue
) {
this.randomValue = this.getRandom();
}
this.array.push(this.randomValue);
} else {
this.array.push(this.randomValue);
}
console.log({ ...this.array });
this.randomArray.push({ ind: this.i, val: this.array });
}
}
getRandom() {
const value = Math.floor(Math.random() * 4);
return value;
}
}
console.log(new X().getSeveralArrays());
Also, all those checks can be simplified:
while (this.array.some(value => value === this.randomValue)) {
this.randomValue = this.getRandom();
}
And generally, just as a small code review: If you do not need to access something from another class method (or somewhere outside), you should not put all those values into class properties, but keep them as values inside your functions.
So this.randomValue and this.array should just be defined within your function with let or const.

Filter for nested objects to return all children elements

I have a filter that is on ng-repeat and compares strings of all objects (including nested ones) to a search string. If the search string is found in the object, it returns true.
I'm looking for a way to extend this functionality so that when the search string matches with a string in the object, the filter will return true for that object and will return true for all nested objects in the matching object (this is a tree view, I'm searching for a node and want to show all children nodes when matched).
How would I do that?
My filter looks like this:
.filter('deepFilter', function ($filter) {
return function(text) {
return function (value) {
if(text && text.length > 0) {
var searchTerm = text;
if (angular.isObject(value)) {
var found = false;
angular.forEach(value, function(v) {
found = found || $filter('deepFilter')(searchTerm)(v);
});
return found;
} else if (angular.isString(value)) {
if (value.indexOf(searchTerm) !== -1) {
return true;
} else {
return false;
}
}
} else {
return true;
}
};
};
});
The solution I found is by using a function in the isString part of the filter, and iterating over the collection. If I find the object, I look for it's children using a recursive function and set a visibleAsAChild property for these. Then, I've added a condition in the isObject evaluation to return true for these object that have visibleAsAChild prop.
I'm not sure if this is the most efficient way to do it, but it certainly works.
.filter('deepFilter', function ($filter) {
var currentObject;
var setChildrenToVisible = function(node) {
angular.forEach(node.nodes, function(node) {
if(node.nodes) {
setChildrenToVisible(node);
}
node.visibleAsAChild = true;
});
};
var lookupChildren = function(o, value) {
// console.log(o);
angular.forEach(o.nodes, function(node) {
if (node.name === value) {
setChildrenToVisible(node);
}
});
};
return function(text) {
return function (value) {
if(text && text.length > 0) {
var searchTerm = text;
if (angular.isObject(value)) {
var found = false;
angular.forEach(value, function(v) {
found = found || $filter('deepFilter')(searchTerm)(v);
});
if(found && value.hasOwnProperty('id')) {
currentObject = value;
}
if(value.hasOwnProperty('id') && value.visibleAsAChild) {
return true;
}
return found;
} else if (angular.isString(value)) {
if (value.indexOf(searchTerm) !== -1) {
if(currentObject){
lookupChildren(currentObject, value);
}
return true;
} else {
return false;
}
}
} else {
return true;
}
};
};

Return single true from function in AngularJS

I'm having a bit of trouble getting my function to return a single true or false
Basically I have an array like below;
orderItem contains menu_modifier_groups which contains menu_modifier_items
Now each menu_modifier_groups has an attribute max_selection_points.
The menu_modifier_items are displayed using ng-repeat with checkboxes and have an ng-model = item.selected
What I want to do is to be able to loop through all menu_modifier_groups and determine if the required menu_modifier_items have been selected by comparing them to max_selection_points of each menu_modifier_groups
This is what I have so far
$scope.isValid = function (orderItem) {
var count = 0;
angular.forEach(orderItem.menu_modifier_groups, function(group) {
angular.forEach(group.menu_modifier_items, function (item) {
count += item.selected ? 1 : 0;
});
if (count != group.max_selection_points) {
return false;
} else if (count == group.max_selection_points) {
return true;
}
});
}
Any help/advice appreciated
Your code should be
$scope.isValid = function(orderItem) {
//By default make it false
var IsAllSelected = false;
angular.forEach(orderItem.menu_modifier_groups, function(group) {
var count = 0;
angular.forEach(group.menu_modifier_items, function(item) {
count += item.selected ? 1 : 0;
});
if (count == group.max_selection_points) {
IsAllSelected = true;
} else {
//if one item failed All select do return false
IsAllSelected = false;
}
});
return IsAllSelected;
}

How do I watch every object in a collection?

In an Angular scope, I have a collection of objects that carry some data, plus x and y coordinates. Some other scope variables must be recomputed based on the x and y values. What is the best way to do it efficiently?
If I use $scope.$watch(collection, handler) or $scope.$watchCollection(collection, handler), I don't get notified about changes to the objects it contains.
If I use $scope.$watch(collection, handler, true), I do get notified, but when anything changes, not only x and y. Plus, I don't know which element of the collection was changed (and I imagine that this deep comparison is rather costly).
Ideally, I would like to write something like $scope.$watchObjects(collection, ["x", "y"], handler), where my handler would be called with the changed object and possibly its index. Is there an easy way to do that?
Could you do:
angular.forEach(colletion, function(object) {
$scope.$watch(object, function() {
... I'm not sure what would you like to do with object here...
})
}, true)
I am pretty sure it was in this video: https://www.youtube.com/watch?v=zyYpHIOrk_Y
but somewhere I saw Angular devs talking about mapping the data you are watching to a smaller subset, something like this maybe:
$scope.$watchCollection(function() {
return yourList.map(function(listItem) {
return { 'x': listItem.x, 'y': listItem.y };
};
}, function(newVal, oldVal) {
// perform calculations
});
That would leave you $watching just an array of objects having x and y properties.
$scope.$watch('collection', function() {
...
}, true);
Keep in mind that the collection must be declared on the $scope.
Based on Slaven Tomac’s answer, here's what I came up with. Basically: this uses a $watchCollection to detect when items are inserted or added on the collection. For each added item, it starts monitoring it. For each removed item, it stops monitoring it. It then informs a listener each time an object changes.
This further allows to refine what should be considered as a change in the object itself or a change in the collection only. The sameId function is used to test whether two objects a and b should be considered to be the same (it could just a === b, but it could be something more sophisticated — in particular, if you pass in a field name as the sameId argument [e.g., "id"], then two objects will be considered to be “the same.”)
The createArrayDiffs is adapted from a similar change-detection method in the Eclipse Modeling Framework and is interesting in its own right: it returns a list of changes that happened between an array and another array. Those changes are insertions, removals, and object changes (according to the passed fields).
Sample usage:
watchObjectsIn($rootScope, "activities", "id", ["x", "y"], function (oldValue, newValue) {
console.log("Value of an object changed: from ", oldValue, " to ", newValue);
});
Of course, I'm interested in any simpler and/or more efficient solution!
Implementation (compiled TypeScript):
function watchObjectsIn(scope, expr, idField, watchedFields, listener) {
var fieldCompareFunction = makeFieldCompareFunction(watchedFields);
var unbindFunctions = [];
function doWatch(elem, i) {
var unbindFunction = scope.$watch(function () {
return elem;
}, function (newValue, oldValue) {
if (newValue === oldValue)
return;
if (!fieldCompareFunction(oldValue, newValue))
listener(oldValue, newValue);
}, true);
unbindFunctions.push(unbindFunction);
}
function unwatch(elem, i) {
unbindFunctions[i]();
unbindFunctions.splice(i, 1);
}
scope.$watchCollection(expr, function (newArray, oldArray) {
if (isUndef(newArray))
return;
var diffs = createArrayDiffs(oldArray, newArray, idField, fieldCompareFunction);
if (diffs.length === 0 && newArray.length !== unbindFunctions.length) {
for (var i = unbindFunctions.length - 1; i >= 0; i--) {
unwatch(null, 0);
}
diffs = createArrayDiffs([], newArray, idField);
}
_.forEach(diffs, function (diff) {
switch (diff.changeType()) {
case 0 /* Addition */:
doWatch(diff.newValue, diff.position);
break;
case 1 /* Removal */:
unwatch(diff.oldValue, diff.position);
break;
case 2 /* Change */:
listener(diff.oldValue, diff.newValue);
break;
}
});
});
}
function isUndef(v) {
return typeof v === "undefined";
}
function isDef(v) {
return typeof v !== "undefined";
}
function parseIntWithDefault(str, deflt) {
if (typeof deflt === "undefined") { deflt = 0; }
var res = parseInt(str, 10);
return isNaN(res) ? deflt : res;
}
function cssIntOr0(query, cssProp) {
return parseIntWithDefault(query.css(cssProp));
}
function randomStringId() {
return Math.random().toString(36).substr(2, 9);
}
var ArrayDiffChangeType;
(function (ArrayDiffChangeType) {
ArrayDiffChangeType[ArrayDiffChangeType["Addition"] = 0] = "Addition";
ArrayDiffChangeType[ArrayDiffChangeType["Removal"] = 1] = "Removal";
ArrayDiffChangeType[ArrayDiffChangeType["Change"] = 2] = "Change";
})(ArrayDiffChangeType || (ArrayDiffChangeType = {}));
var ArrayDiffEntry = (function () {
function ArrayDiffEntry(position, oldValue, newValue) {
this.position = position;
this.oldValue = oldValue;
this.newValue = newValue;
}
ArrayDiffEntry.prototype.changeType = function () {
if (isUndef(this.oldValue))
return 0 /* Addition */;
if (isUndef(this.newValue))
return 1 /* Removal */;
return 2 /* Change */;
};
return ArrayDiffEntry;
})();
function makeFieldCompareFunction(fields) {
return function (o1, o2) {
for (var i = 0; i < fields.length; i++) {
var fieldName = fields[i];
if (o1[fieldName] !== o2[fieldName])
return false;
}
return true;
};
}
function createArrayDiffs(oldArray, newArray, sameId, sameData, undefined) {
if (isUndef(sameId)) {
sameId = angular.equals;
} else if (_.isString(sameId)) {
var idFieldName = sameId;
sameId = function (o1, o2) {
return o1[idFieldName] === o2[idFieldName];
};
}
var doDataChangedCheck = isDef(sameData);
if (doDataChangedCheck && !_.isFunction(sameData)) {
if (_.isString(sameData))
sameData = [sameData];
var fieldsToCheck = sameData;
sameData = makeFieldCompareFunction(fieldsToCheck);
}
var arrayDiffs = [];
function arrayIndexOf(array, element, index) {
for (var i = index; i < array.length; i++) {
if (sameId(array[i], element))
return i;
}
return -1;
}
var oldArrayCopy = oldArray ? oldArray.slice() : [];
var index = 0;
var i;
for (i = 0; i < newArray.length; i++) {
var newValue = newArray[i];
if (oldArrayCopy.length <= index) {
arrayDiffs.push(new ArrayDiffEntry(index, undefined, newValue));
} else {
var done;
do {
done = true;
var oldValue = oldArrayCopy[index];
if (!sameId(oldValue, newValue)) {
var oldIndexOfNewValue = arrayIndexOf(oldArrayCopy, newValue, index);
if (oldIndexOfNewValue !== -1) {
var newIndexOfOldValue = arrayIndexOf(newArray, oldValue, index);
if (newIndexOfOldValue === -1) {
arrayDiffs.push(new ArrayDiffEntry(index, oldValue, undefined));
oldArrayCopy.splice(index, 1);
done = false;
} else if (newIndexOfOldValue > oldIndexOfNewValue) {
if (oldArrayCopy.length <= newIndexOfOldValue) {
newIndexOfOldValue = oldArrayCopy.length - 1;
}
arrayDiffs.push(new ArrayDiffEntry(index, oldValue, undefined));
oldArrayCopy.splice(index, 1);
arrayDiffs.push(new ArrayDiffEntry(newIndexOfOldValue, undefined, oldValue));
oldArrayCopy.splice(newIndexOfOldValue, 0, oldValue);
done = false;
} else {
arrayDiffs.push(new ArrayDiffEntry(oldIndexOfNewValue, newValue, undefined));
oldArrayCopy.splice(oldIndexOfNewValue, 1);
arrayDiffs.push(new ArrayDiffEntry(index, undefined, newValue));
oldArrayCopy.splice(index, 0, newValue);
}
} else {
oldArrayCopy.splice(index, 0, newValue);
arrayDiffs.push(new ArrayDiffEntry(index, undefined, newValue));
}
} else {
if (doDataChangedCheck && !sameData(oldValue, newValue)) {
arrayDiffs.push(new ArrayDiffEntry(i, oldValue, newValue));
}
}
} while(!done);
}
index++;
}
for (i = oldArrayCopy.length; i > index;) {
arrayDiffs.push(new ArrayDiffEntry(--i, oldArrayCopy[i], undefined));
}
return arrayDiffs;
}

Resources