Suppose I have an array of objects like this:
$scope.data = [
{
"active": true,
"id": "4bcb19db-ec10-4de5-8ae6-b13388494651",
"name": "Owais Shah",
"username": "chekhov"
},
{
"active": false,
"id": "630d0582-1bf9-4331-9fa6-f6baa59319b2",
"name": "Tim Hunt",
"username": "one"
}
]
I can use this to deep watch the object:
$scope.$watch('data', function(){...}, true)
However, this is not efficient in my case, as the only property of these objects that I'm interest in watching is active.
Is there a way to watch only a specific property deep inside the object? Something like $scope.$watch('data[].active' ...)
I think you could do something like this:
for(var i=0,j=$scope.data.length;i<j;i++) {
$scope.$watch(data[i].active, function() { });
}
You can watch on a function which in turn returns your inner objects
$scope.$watch( function() {
return $scope.data.active[0];
},
function(newValue, oldValue) {
console.log('It changed!');
}
);
You can take advantage of the $watch function accepting a function and return a value you compute from the array. For example, you could look at it is a number based on the binary sequence of 0/1 for active = false or true. Something like this would work:
$scope.$watch(function() {
var value = 0, idx;
for (idx = 0; idx < $scope.data.length; idx += 1) {
value *= 2;
value += $scope.data[idx].active ? 1 : 0;
};
return value;
}, function() {
$scope.changes += 1;
$scope.status = "Changed " + $scope.changes + " times."; });
The jsFiddle here illustrates it: http://jsfiddle.net/jeremylikness/GrRtK/
You can see it illustrated by clicking the same button. The first time the element changes from true to false or vice versa, the change is registered, while subsequent updates do not trigger the watch. It will be even more readable if you use a mapping function from underscore to project the array onto your number or value to watch.
Related
The below block is my JSON. The -KTYLrHDHt234rFDNHrm type hashes are generated by the supplied API of the client. I think they're using Firebase.
I am passing a query in the URL which contains the pageId for each of those nested objects.
Example https://cms.app.io/edit/Nike3243
But since the hash is auto generated how can I search through all the JSON and check if the pageId matches my Angular route and then only return the values of the same child.
{
"-KTYLrHDHtdq23423NHrm": {
"pageCreation": "10/8/2016, 14:14:22 PM",
"pageGallery": {
"slider_1_img": "http://",
"slider_2_img": "http://",
},
"pageId": "Nike13243",
"pageName": "Nike Campaign 1",
"store": "11"
},
"-KTYLrHDHtdqirFDNHrm": {
"pageCreation": "10/8/2016, 12:14:22 AM",
"pageGallery": {
"slider_1_img": "http://",
"slider_2_img": "http://",
},
"pageId": "Nike323243",
"pageName": "Nike Campaign 2",
"store": "12"
},
"-KTYLrHDHt234rFDNHrm": {
"pageCreation": "10/8/2016, 13:14:22 PM",
"pageGallery": {
"slider_1_img": "http://",
"slider_2_img": "http://",
},
"pageId": "Nike3243",
"pageName": "Nike Campaign 3",
"store": "13"
}
}
So I want to return only the data of Nike3243 but I want to return the store, the slider and the pageName. How can I do this since the KTYLrHDHt234rFDNHrm hash is something I will never know
cmsApp.controller('pages-edit', function ($scope, $http, $routeParams) {
var pageIdU = $routeParams.id;
$http.get(firebase_url+'cms/home.json'+randstatus).success(function(data) {
$scope.pages = data;
// this would be pageId = Nike3243
console.log(data.pageIdURI.pageName[pageId]);
});
});
Thanks
Assuming your object is called 'objTest', you could do something like this:
var strPageId = 'The page id to find', objFound;
for( var strKey in objTest ) {
var objTemp = objTest[strKey];
if ( objTemp['pageId'] == strPageId ) {
objFound = objTemp;
break;
}
}
if ( typeof objFound == "object" ) {
//Do something...object has been found!
}
I actually did this:
$.each(data, function (bb) {
var crossReference = data[bb].transId;
if (crossReference==pageIdUri) {
$scope.transNameEn = data[bb].transNameEn;
$scope.transNameArabic = data[bb].transNameArabic;
$scope.transCreation = data[bb].transCreation;
$scope.transModified = data[bb].transModified;
$scope.notes = data[bb].notes;
}
});
It may be easier for you to simply transform the data rather than perform this search over and over. You can loop through the data once and get it in the correct format.
I would do something like:
var recordLookup = {};
for (var id in records) {
var pageId = records[id].pageId;
recordLookup[pageId] = record[id];
recordLookup[pageId].originalId = id;
}
This way you can now easily look up any record by its page id. If you need to send the data back to the server in the original form you still have that original id and can do whatever you need to in order to get it in the proper format. This way you loop once or maybe twice, (once to make the lookup and once to revert at the end of your operation) rather than any time you need to get data out of your records.
I am working in an application where the user can provide a JSON formly form definition that is saved in the DB. Eventually, that definition will be used to generate a form.
I need to validate that JSON definition before saving it into the DB, something like what API-check does underneath.
Does angular-formly have something to do that or should I create my own validator?
Thanks.
Yes, you can see it at http://angular-formly.com/#/example/advanced/validators
Example with JSON:
{
"key": "test",
"className": "flex-gt-xs-100 flex",
"type": "input",
"templateOptions": {
"required": true
},
"validation": {
"messages": {
"required": "function(viewValue, modelValue, scope) {return scope.to.label + ' is required';}"
}
}
}
But JSON don't understand function. So, you need write code to do that
$http.get('test.json').success(function(array)
{
var jsonTransformed = JSON.parse(JSON.stringify(array), function (key, value) {
if (value && (typeof value === 'string') && value.indexOf("function") === 0) {
var jsFunc;
eval("jsFunc = " + value);
return jsFunc;
}
return value;
});
vm.fields = jsonTransformed;
});
You should write validate function in controller and declare it in formly-field
I'm trying to figure out how to watch an array and update an object, like this.
var vm = this; //controller
vm.order = {
totalValue: 25,
products: [{id:0, val: 10, qtd: 1}, {id:1, val: 15, qtd: 1}]
};
If I push a new product into this order object, how do I update the totalValue just watching the collection ? (totalValue = All Val * All Qtd)
It is necessary because the "qtd" field is bind to an input field, so I can change the value of all "products" at any time.
UPDATE
$scope.$watchCollection(
function() { return self.order.products; },
function(products) {
self.order.totalValue = products.reduce(function (p, n) {
console.log(n.qtd);
console.log(n.val);
return p + n.qtd * n.val;
}, 0);
});
The code worked, but only when I push a new "product" into order.
I have to watch every order.products.product.qtd and change the order.totalValue every time the user change the qtd of a product
Try this way:
$scope.$watch('order.products', function (products) {
scope.order.totalValue = products.reduce(function (p, n) {
return p + n.qtd * n.val;
}, 0);
}, true);
This will deep watch array to detect qtd and val changes inside array items.
I have the following model:
{
"RU": {
name: "value a",
other_key: "xxx",
},
"EN": {
name: "value a",
other_key: "xxx",
},
... 10+ other languages ...
}
To render form I went with intermediate format:
[
{ name: "value_a", languages: ["RU", "EN"] }
... other groups by value ...
]
How do I keep this all in sync? Changes in the original model may occur and
I should push them into intermediate format to update view. Changes in the
view may be made by a user and I should propagate them to the model and
intermediate view.
Can not think of good solution without tons of watchers and complicated code.
Ideas?
You will only to watch the model & the intermediate format ( do full watch with 3rd parameter set as true - deep watch ).
var surpressWatchmodel = false;
var surpressWatchinter = false;
$scope.$watch('model',function(newval,oldval){
if(surpressWatchmodel) { surpressWatchmodel = false; return; }
//update intermediate format.
surpressWatchinter = true;
},true);
$scope.$watch('intermediate',function(newval,oldval){
if(surpressWatchinter ) { surpressWatchinter = false; return; }
//update model format.
surpressWatchmodel= true;
},true);
for view you will use ng-model="intermediate.name" ( angular will add this as a watch item for you ).
I am trying to create array from database objects :
I have entity "group" wich hasMany "devices", I want to create array whit all groups and for each groups the list of his devices :
[
{
"group_id": “1”,
"name": “My_group”,
"devices_list": [1, 2, 18]
},
{
"group_id": “2”,
"name": “My_second_group”,
"devices_list": [3, 24]
}
]
I tried several ways like this :
Group.all(function (err, groups) {
var resJson = {};
groups.forEach(function(group, index){
group.devices(function(err, devices){
resJson[index] = group;
console.log(devices);
resJson[index].devices_list = devices;
//End of the loop
if (index == groups.length -1){
send({code: 200, data: resJson});
}
});
});
});
EDIT 1 :
I tried this way too :
var resJson = {};
groups.forEach(function(group, index){
group.devices(function(err, devices){
resJson[index] = group;
resJson[index].devices_list = [];
devices.forEach(function(device,index2){
resJson[index].devices_list.push(device);
});
//End of the loop
if (index == groups.length -1){
send({code: 200, data: resJson});
}
});
});
But finally, my resJson contains only empty groups (groups without device associated), the other groups are not visible. Thus my devices_list are all empty whereas the console.log(devices) display devices.
It seems that the "send" instruction is processed before the treatment of non-empty groups.
What is the rigth way to do this ?
Thank you for your time
Instead of tracking and using an index against the length of the list perhaps you could use an after type of construct. I really enjoy them and they're easy to integrate and serve the perfect purpose for doing something after a set number of times.
First, lets define an after function you can use.
var after = function(amount, fn) {
var count = 0;
return function() {
count += 1;
if (count >= amount) {
fn.apply(arguments);
}
};
};
That should work for you now, let's modify your code sample to use this.
var json = []; // To return, you originally wanted an array.
Group.all(function(err, groups) {
if (err) {
// Handle the error
} else {
var sendJson = after(groups.length, function(json) {
send({code: 200, data: json});
});
groups.forEach(function(group) {
group.devices(function(err, devices) {
if (err) {
// Handle the error...
} else {
group.devices_list = devices;
json.push(group); // This part is different, using this method you'll match the JSON you gave as your "goal"
}
// This is outside the if/else because it needs to be called for every group
// regardless of change. If you do not call this the exact number of times
// that you specified it will never fire.
sendJson(json);
});
}
});
Perhaps something like that might clear up your issue.