I would like to extend some properties recursive (aka. deep copy).
much like jQuery does. I'm not including jquery only b/c of one thing.
jQuery.extend( true, target, object1 )
is there any elegant way you know of that does it with simple javascript or angularjs?
update
please take a look and try to accomplish the same result
http://plnkr.co/edit/GHabYbyhsqtfBPtplksO?p=preview
i did look into .copy() but the "properties (for objects) are deleted"
Here is an extendDeep function based off of the angular.extend function. If you add this to your $scope, you would then be able to call
$scope.meta = $scope.extendDeep(ajaxResponse1.myMeta, ajaxResponse2.defaultMeta);
and get the answer you are looking for.
$scope.extendDeep = function extendDeep(dst) {
angular.forEach(arguments, function(obj) {
if (obj !== dst) {
angular.forEach(obj, function(value, key) {
if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
extendDeep(dst[key], value);
} else {
dst[key] = value;
}
});
}
});
return dst;
};
Note: This function has the side-effect of copying values from later arguments into the earlier arguments. For a simple fix to this side effect, you can change dst[key] = value to dst[key] = angular.copy(value).
All the answers here are valid for versions of Angular before 1.4
As of Angular 1.4, you can use angular.merge to do exactly that:
Unlike extend(), merge() recursively descends into object properties of source objects, performing a deep copy.
https://docs.angularjs.org/api/ng/function/angular.merge
function deepExtend(destination, source) {
for (var property in source) {
if (source[property] && source[property].constructor &&
source[property].constructor === Object) {
destination[property] = destination[property] || {};
arguments.callee(destination[property], source[property]);
} else {
destination[property] = source[property];
}
}
return destination;
}
Plunker
Src: https://gist.github.com/gregdangelo/2343158
Building on Ryan's code, you can shorten the object check and you should also NOT extend functions so you don't override object pointers.
var extendDeep = function extendDeep(dst) {
angular.forEach(arguments, function(obj) {
if (obj !== dst) {
angular.forEach(obj, function(value, key) {
if (dst[key] && angular.isObject(dst[key])) {
extendDeep(dst[key], value);
} else if(!angular.isFunction(dst[key])) {
dst[key] = value;
}
});
}
});
return dst;
};
The same solution as Ryan but with support for array merge
function extendDeep(dst) {
angular.forEach(arguments, function (obj) {
if (obj !== dst) {
angular.forEach(obj, function (value, key) {
if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
extendDeep(dst[key], value);
} else if (dst[key] && dst[key].constructor && dst[key].constructor === Array) {
dst[key].concat(value);
} else if(!angular.isFunction(dst[key])) {
dst[key] = value;
}
}
);
}
}
);
return dst;
}
Angular has a copy method:
angular.copy
Related
I'm using $filter to iterate through an array and fetch a specific value
Below is my code:
var selected = $filter('filter')($scope.folders, {url: el.selected[0] });
This code is working, but I got a problem when the url contain an accent and space like so :
/Users/Me/project/products/Poste à souder
In that case the string comparaison isn't working anymore.
What is the cleaner way to solve this situation ?
That true. As a francophone, I've often encounter encoding/decoding issues with angularjs.
The source code of the default filter is as follow
function filterFilter()
{
return function(array, expression, comparator)
{
if (!isArrayLike(array))
{
if (array == null)
{
return array;
}
else
{
throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
}
}
var expressionType = getTypeForFilter(expression);
var predicateFn;
var matchAgainstAnyProp;
switch (expressionType)
{
case 'function':
predicateFn = expression;
break;
case 'boolean':
case 'null':
case 'number':
case 'string':
matchAgainstAnyProp = true;
//jshint -W086
case 'object':
//jshint +W086
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
break;
default:
return array;
}
return Array.prototype.filter.call(array, predicateFn);
};
}
and the predicate generator stand as follow: it generate the default comparator if the provided one is not a function
function createPredicateFn(expression, comparator, matchAgainstAnyProp)
{
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
var predicateFn;
if (comparator === true)
{
comparator = equals;
}
else if (!isFunction(comparator))
{
comparator = function(actual, expected)
{
if (isUndefined(actual))
{
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null))
{
// No substring matching against `null`; only match against `null`
return actual === expected;
}
if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual)))
{
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
actual = lowercase('' + actual);
expected = lowercase('' + expected);
return actual.indexOf(expected) !== -1;
};
}
predicateFn = function(item)
{
if (shouldMatchPrimitives && !isObject(item))
{
return deepCompare(item, expression.$, comparator, false);
}
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
};
return predicateFn;
}
Too much speech. You have the choice:
Provide a comparator to your filter see the doc
but remember that you can't define inline function in angular template
you can define a function in that scope, but it will only be available in that scope
You can write your own filter
.filter('myCustomFilter', function()
{
return function(input, criteria)
{
... // your logic here
return ...// the filtered values
};
})
Maybe it's best to write your own filter:
app.filter("customFilter", function () {
//the filter will accept an input array, the key you want to look for and the value that the key should have
return function (array, key, value) {
return array.filter(function(x){
return (x.hasOwnProperty(key) && (x[key] === value));
});
};
});
And use it in your controller like:
$scope.filtered = $filter("customFilter")($scope.folders, "url", "/Users/Me/project/products/Poste à souder");
Check out a working demo here.
I am very new to angular and, I am not sure how to control the behavior of my filters.
In the app, I have two different single-select drop down controls that filter the results of my data set and fill a table. However, even though these filters work, the results are dependent of both controls and if both are not being used , the empty set is returned. So, my question is: How can I use these filters optionally? So, the app returns every result when the filters are not used or returns the filtered results by one of the controls or both?
Thank you
Here is the code:
AngularJS
The filters for each control. They look very similar:
.filter('byField', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
for (var i in this.options) {
if ((options[i].value === value.fieldId &&
options[i].name === "Field" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
}, items);
return items.out;
};
})
.filter('byClass', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
for (var i in this.options) {
if ((options[i].value === value.documentClass &&
options[i].name === "Class" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
}, items);
return items.out;
};
})
HTML
This is what I am doing to populate the rows of the table:
<tr ng-repeat="result in results | byField:outputFields | byClass:outputClasses">
<td>{{result.documentId}}</td>
...
</tr>
Dorado7.1 in all event listeners provides a view implicit variable pointing to the current event host's view, the variable can completely replace the use of this scenario.
Well, as I imagined the answer was more related to set theory than to angular.
I just made an union between the empty set and every result, and it worked.
.filter('byField', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
if (options.length) {
for (var i in this.options) {
if ((options[i].value === value.fieldId &&
options[i].name === "Field" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
} else {
this.out = results.slice();
}
}, items);
return items.out;
};
})
I have a problem on parsing of my JSON data. On my object 2, I would have the "t_quartier" while the value is just a reference that points to the object 1.
How can I get this value if I'm on my item 2?
thank you a lot
You could use this:
angular.module('app').service('commonService', commonService);
function commonService() {
//DFS for fixing JSON references
var elements = {}
this.fixReferences = function (json) {
var tree = json;
for (var x in tree) {
if ((typeof (tree[x]) === 'object') && (tree[x] !== null)) {
var result = dfsVisit(tree[x]);
tree[x] = result;
}
}
return tree;
}
function dfsVisit(tree) {
for (var x in tree) {
if ((typeof (tree[x]) === 'object') && (tree[x] !== null)) {
var result = dfsVisit(tree[x]);
tree[x] = result;
}
}
if (tree["$ref"] !== undefined) {
var ref = tree.$ref;
if (elements[ref] !== undefined) {
tree = elements[ref];
}
} else if (tree["$id"] !== undefined) {
var element = tree;
elements[element.$id] = element;
}
return tree;
}
}
You could define that function wherever you want but a service would be a clean way.
For using it:
angular.module('app').factory('yourService', yourService);
/*#ngInject*/
function yourService($http, commonService) {
var service = {
get: get
};
return service;
function get() {
return $http.get('Your url').then(function (response) {
var fixedData = commonService.fixReferences(response.data);
return fixedData;
});
}
}
I have items which should have multiple (e.g. categories). Now I want to filter my items to these categories.
I think the task is not possible with the filter-directive without using a custom filter, right?
I came up with a solution, but it looks dirty and wrong to me:
$scope.filterList = function (item) {
var found = false;
var allFalse = true;
angular.forEach(item.attributes, function (value, key) {
if ($scope.activeAttributes[value.name] === true) {
found = true;
}
});
angular.forEach($scope.activeAttributes, function (value, key) {
if (value === true) {
allFalse = false;
}
});
$log.log("length: " + Object.keys($scope.activeAttributes).length);
if (found === true || Object.keys($scope.activeAttributes).length === 0 || allFalse === true) {
return true;
}
};
Demo JSFiddle of my code
I thought with Angular, that the code should be simple and most of the work should be done by Angular. What if I need to filter more attributes?
In the codebase that I am trying to figure out, I see that a js file (myloopfile.js) being imported into another js file. I am trying to make sense of some of the code used there
this is myloopfile.js
function method1(value) {
// return something
}
var myLooper = function (obj, iterator, context) {
var key;
if (obj) {
if (typeof obj === 'function') {
for (key in obj) {
if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
iterator.call(context, obj[key], key);
}
}
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context);
} else if (isArrayLike(obj)) {
for (key = 0; key < obj.length; key++)
iterator.call(context, obj[key], key);
} else {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key);
}
}
}
}
return obj;
};
……………………………………………………………………………….
the myLoop in myloopfile.js is called like this
var looper = require(‘../myloopfile.js);
looper({
loop1: function(Home) { //do something },
loop2: function(Home) { //dosomething }
}, function(return1, return2) {
//do something else
});
I am trying to find out where this
function(return1, return2) {
//do something else
});
coming from ? I don’t see anything in that file that suggests that there is a method attached to it. Also where are the parameters return1 and return2 coming from? is this some javascript way to attach things ?
var myLooper = function (obj, iterator, context) {
/* .... */
iterator.call(context, obj[key], key);
/* .... */
};
You pass:
looper({
loop1: function(Home) { //do something },
loop2: function(Home) { //dosomething }
}, function(return1, return2) {
//do something else
});
So
obj = {
loop1: function(Home) { //do something },
loop2: function(Home) { //dosomething }
}
and
iterator = function(return1, return2) {
//do something else
}
The Function.prototype.call() method calls a function with a given this value and arguments provided individually. Therefore, inside you iterator function:
this = context;
return1 = obj[key];
return2 = key;
So javascript has function that are called anonymous function that don't need a function name.
Basically it is used (in this instance) as a way to be an expantion of a parameter.
Take for example the javascript function setTimeout
Well setTimeout can take an anonymous function as one of its parameters i.e
var timer = setTimeout(function{
//do something
},
2000);
// setTimeout(function, time, paramters)
So you don't have to declare a function and pass it in as a parameter
Back to your case, you have this anonymous function that takes return1 and return2
So in the end:
return1 = obj[key];
return2 = key;