Checking if object is empty, works with ng-show but not from controller? - angularjs

I have a JS object declared like so
$scope.items = {};
I also have a $http request that fills this object with items. I would like to detect if this item is empty, it appears that ng-show supports this... I enter
ng-show="items"
and magically it works,I would also like to do the same from a controller but i can't seem to get it to work, it appears I may have to iterate over the object to see if it has any properties or use lodash or underscore.
Is there an alternative?
I did try
alert($scope.items == true);
but it always returns false , when the object is created and when populated with $http, so its not working that way.

Or you could keep it simple by doing something like this:
alert(angular.equals({}, $scope.items));

In a private project a wrote this filter
angular.module('myApp')
.filter('isEmpty', function () {
var bar;
return function (obj) {
for (bar in obj) {
if (obj.hasOwnProperty(bar)) {
return false;
}
}
return true;
};
});
usage:
<p ng-hide="items | isEmpty">Some Content</p>
testing:
describe('Filter: isEmpty', function () {
// load the filter's module
beforeEach(module('myApp'));
// initialize a new instance of the filter before each test
var isEmpty;
beforeEach(inject(function ($filter) {
isEmpty = $filter('isEmpty');
}));
it('should return the input prefixed with "isEmpty filter:"', function () {
expect(isEmpty({})).toBe(true);
expect(isEmpty({foo: "bar"})).toBe(false);
});
});
regards.

Use an empty object literal isn't necessary here, you can use null or undefined:
$scope.items = null;
In this way, ng-show should keep working, and in your controller you can just do:
if ($scope.items) {
// items have value
} else {
// items is still null
}
And in your $http callbacks, you do the following:
$http.get(..., function(data) {
$scope.items = {
data: data,
// other stuff
};
});

another simple one-liner:
var ob = {};
Object.keys(ob).length // 0

If you couldn't have the items OBJ equal to null, you can do this:
$scope.isEmpty = function (obj) {
for (var i in obj) if (obj.hasOwnProperty(i)) return false;
return true;
};
and in the view you can do:
<div ng-show="isEmpty(items)"></div>
You can do
var ob = {};
Object.keys(ob).length
Only if your browser supports ECMAScript 5. For Example, IE 8 doesn't support this feature.
See http://kangax.github.io/compat-table/es5/ for more infos

if( obj[0] )
a cleaner version of this might be:
if( typeof Object.keys(obj)[0] === 'undefined' )
where the result will be undefined if no object property is set.

Or, if using lo-dash: _.empty(value).
"Checks if value is empty. Arrays, strings, or arguments objects with a length of 0 and objects with no own enumerable properties are considered "empty"."

Check Empty object
$scope.isValid = function(value) {
return !value
}

you can check length of items
ng-show="items.length"

Related

JavaScript array default assignment syntax confusion

If I create a function constructor in JavaScript like so:
function Emitter() {
this.events={}
}
and then I add a method to its prototype like so:
Emitter.prototype.on = function(type,listener) {
this.event[type] = this.events[type] | [];
this.event[type].push(listener);
}
When I call the method 'on' an instance of Emitter twice, why does it not just overwrite the original property called greet and assign it the second function? I guess I do not understand the stricture of what is happening in:
this.event[type] = this.events[type] | [];
var emtr = new Emitter();
emtr.on('greet',function(){
console.log('Hello once');
});
emtr.on('greet', function(){
console.log('Hello twice');
});
You should be using || for OR instead of |, this is invalid:
this.event[type] = this.events[type] | [];
Also, you are calling event instead of events. You should have:
this.events[type] = this.events[type] || [];
This way if this.events[type] is not undefined it will stay as is. However if it is undefined is will be assigned as an empty array: []
The code below will successfully add the two functions to emtr.events['greet'] (an array of functions):
function Emitter() {
this.events = {}
}
Emitter.prototype.on = function(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type].push(listener);
}
var emtr = new Emitter();
emtr.on('greet', function() {
console.log('Hello once');
});
emtr.on('greet', function(){
console.log('Hello twice');
});
console.log(emtr.events['greet'])
So you can call them like so:
emtr.events['greet'][0]();
And
emtr.events['greet'][1]()
If instead, you would like to replace the listener then you shouldn't be using an array. Pointing to a function instead of an array of functions will suffice:
function Emitter() {
this.events = {}
}
Emitter.prototype.on = function(type, listener) {
this.events[type] = listener;
}
var emtr = new Emitter();
emtr.on('greet', function() {
console.log('Hello once');
});
emtr.on('greet', function(){
console.log('Hello twice');
});
emtr.events['greet'](); // notice how the first listener was replaced by the new one
This way you can call your listener with emtr.events['greet']().
While #Ivan is completely correct, he missed your main question of “why?”
The answer is that the logical OR operator in JS also functions as a null coalescing operator when used as part of an assignment statement.
Essentially, in the case
let x = y || “default”;
The right hand side will evaluate to the first “truthy” value and return that. In JS most things evaluate true in a logical operation except values like false, 0, null, and a few others not relevant to this question.
So in your syntax (as corrected by Ivan), you’re telling JS to assign to the events[type] property the first thing that’s true. Either itself (which will evaluate true if it’s not null, unassigned, 0, etc) or else an empty array.
The first time you add an event, it’ll be unassigned and therefore get an empty array added. Subsequently, arrays eval as true, so you’ll just keep re-assigning the property to itself which has no effect.
Make sense?
function Emitter() {
this.events = []
}
Emitter.prototype.on = function (type, listener) {
this.events[type] = this.events[type]||[]
this.events[type].push(listener);
}
Emitter.prototype.emit = function(type){
var listener = this.events[type].pop()
if(this.events[type].length>=0)
listener()
else
console.log('Nothing to emit')
}
var emtr = new Emitter();
emtr.on('greet',function(){
console.log('Hello once');
});
emtr.emit('greet')

AngularJS: How to create new arrays from JSON response?

I have a function tied to the factory for my controller:
fac.GetDailyCountersList = function () {
return $http.get('/Data/GetDailyCountersList')
}
Inside the controller, here's where the function is called:
$scope.DailyCounters = null;
DailyCounterService.GetDailyCountersList().then(function (d) {
$scope.DailyCounters = d.data;
}, function (error) {
alert('Error!');
});
Based on this, if I were to output the variable on the html page, this would show:
[{"Id":1,"MIDay":"16","MITime":"900","MICounter":0},
{"Id":2,"MIDay":"16","MITime":"915","MICounter":1},
{"Id":3,"MIDay":"16","MITime":"930","MICounter":0},
{"Id":4,"MIDay":"17","MITime":"945","MICounter":0},
{"Id":5,"MIDay":"17","MITime":"1000","MICounter":0},
{"Id":6,"MIDay":"17","MITime":"1015","MICounter":52},
{"Id":7,"MIDay":"18","MITime":"1030","MICounter":2},
{"Id":8,"MIDay":"18","MITime":"1045","MICounter":3},
{"Id":9,"MIDay":"18","MITime":"1100","MICounter":0}]
My question is how to take this data and create other arrays based on one of the properties of the Json data.
For example, how would I put arrays
{"Id":1,"MIDay":"16","MITime":"900","MICounter":0},
{"Id":2,"MIDay":"16","MITime":"915","MICounter":1},
{"Id":3,"MIDay":"16","MITime":"930","MICounter":0}
Into a new array "ArrayDay16"? And the rest into their respective arrays as well.
I was thinking about doing it this way:
var myArray = [];
if (d.data['MIDay'] == '16')
myArray.push(d.data);
But this is incorrect/fails. Any ideas for an efficient solution?
Use the filter function to populate new arrays. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
//This will populate "someArray" with only the data that has MIDAY = 16
DailyCounterService.GetDailyCountersList().then(function (d) {
$scope.someArray = d.data.filter(function(ele) {
return ele.MIDAY === '16';
});
}
You can use the filter function:
var daySixteen = data.filter(function (d) { return d.MIDay === "16"; });
you can try the next:
d.data.foreach(function(item){
if(item.MIDay == 16)
myArray.push(d.data);
});
If you want to be efficient you should do this on the back end with constraint(s). It's not the browsers job.
Although, if you still want it to filter it on the front end you could use the filter-function with some lamdba expressions
var newList = oldList.filter(i => i["MIDay"] == "16");

Trouble with Controllers to manipulate an Object inside a Service

I have an object which should be accessible in many controllers.
This object is inside a Service, has default values and the controller might change those values later.
My problem is that my object inside the service keep values changed by controllers.
When a controller get the object, I want always that it takes the object with default values. (not with values previously modified by an other controller before...)
I have this inside my service :
this.myObject = {'item1' : 'something', 'item2' : 'other' , .....};
I know that it's not correct because of this.
So I tried to make a method like this :
this.createMyObject = function() {
var obj = myObject;
return obj;
}
And call createMyObject(); in my controllers but this doesn't work too.
I know that the solution might be obvious.
Thanks.
If what you want is a copy of myObject, what you want to do is :
var obj = angular.copy(myObject);
Because var obj = myObject; will just copy the reference of the object, not its content.
Object in Javascript are pass by reference unless copied or cloned. So when you are doing
this.createMyObject = function() {
var obj = myObject;
return obj;
}
The reference of myObject is getting assigned to obj hence, any change in obj will update the myObject as well.
Consider using angular.extend or angular.copy
this.createMyObject = function() {
var obj = {};
angular.copy(myObject, obj);
// or
// obj = angular.copy(myObject);
return obj;
}
Try the below solution:
Service Code:
.service('MyService', function() {
var myObject = {
'item1': '',
'items2': ''
};
/**
* Used to return copy of myObject with some default values
*/
this.createMyObject = function() {
return angular.copy(myObject);
};
this.alterMyObject = function() {
// #TODO here myObject can be used to edit directly
};
});
Note:
"=" operator between two object just used to assign reference of RHS obj to LHS. So any further changes with LHS object will be reflected to RHS obj also.

Angular filter returning an array of objects causing infinite $digest loop

I have a custom filter which returns an array of matches to search field input and it works, but only after causing an infinite $digest loop. This also apparently only began happening after upgrading from Angular 1.0.6. This is the filter code:
angular.module("Directory.searches.filters", [])
.filter('highlightMatches', function() {
var ary = [];
return function (obj, matcher) {
if (matcher && matcher.length) {
var regex = new RegExp("(\\w*" + matcher + "\\w*)", 'ig');
ary.length = 0;
angular.forEach(obj, function (object) {
if (object.text.match(regex)) {
ary.push(angular.copy(object));
ary[ary.length-1].text = object.text.replace(regex, "<em>$1</em>");
}
});
return ary;
} else {
return obj;
}
}
});
I've seen elsewhere that this could be caused by having the filter inside of an ng-show, or that it's because the array being returned is interpreted as a new array every time it's checked, but I'm not sure how I could fix either problem. You can see a production example of this issue at https://www.popuparchive.com/collections/514/items/4859 and the open source project is available at https://github.com/PRX/pop-up-archive. Thank you!
This is happening because of angular.copy(object). Each time the digest cycle runs, the filter returns an array of new objects that angular has never seen before, so the the digest loop goes on forever.
One solution is return an array containing the original items that match the filter, with a highlightedText property added to each item...
angular.module("Directory.searches.filters", [])
.filter('highlightMatches', function() {
return function (items, matcher) {
if (matcher && matcher.length) {
var filteredItems = [];
var regex = new RegExp("(\\w*" + matcher + "\\w*)", 'ig');
angular.forEach(items, function (item) {
if (item.text.match(regex)) {
item.highlightedText = item.text.replace(regex, "<em>$1</em>");
filteredItems.push(item);
}
});
return filteredItems;
} else {
angular.forEach(items, function (item) {
item.highlightedText = item.text;
});
return items;
}
}
});
You can bind to the highlightedText property, something like...
<div>
Results
<ul>
<li ng-repeat="item in items | highlightMatches : matcher" ng-bind-html="item.highlightedText"></li>
</ul>
</div>

Knockout tree - get all selected items in tree

Here's the fiddle
I have a tree structure of clients that I'm binding to an unordered list, and each client may or may not have a SubClient. I've added the ability to select an item in the list but now I cannot figure out how to loop through the tree and get an array of all the selected items.
In particular, this beast is where I'm having problems:
cp.GetSelectedClientsArray = function (clients) {
var selected = [];
ko.utils.arrayForEach(clients, function (item) {
if (item.IsSelected()) {
selected.push(item.ClientName());
}
ko.utils.arrayForEach(item.SubClient(), function (subItem) {
if (subItem.IsSelected()) {
selected.push(subItem.ClientName());
}
cp.GetSelectedClientsArray(subItem);
});
});
console.log(selected);
return selected;
};
After I toggle the IsSelected() observable I'd like to loop through the list and get an array with only the selected items.
I've written and re-written this more than a few times, and could really use some help. I'm not even sure how to write a recursive function that would work, because every time I call the function from within, it wipes out my "selected" array and setting it as a global variable keeps any item that has ever been selected in the array.
Any help is appreciated
Here's recursive version
cp.GetSelectedClientsArray = function (clients) {
var result = [];
function GetSelected(clients){
for (var i in clients){
if(clients[i].IsSelected()){
result.push(clients[i].ClientName());
}
GetSelected(clients[i].SubClient());
}
}
GetSelected(clients);
console.log(result);
return result;
};
See jsfiddle
If I understand correctly what you are trying to do, why not try something like this?
_self.SelectedClient = ko.observableArray();
_self.ToggleSelectedUser = function (data, event) {
var toggle = !data.IsSelected();
data.IsSelected(toggle);
if(toggle === true)
{
_self.SelectedClient.push(data.ClientName());
}
else
{
_self.SelectedClient.remove(data.ClientName());
}
Why walk on the clients list recursively when you can simply create a SelectedClients field on the View-Model, and remove/add to it upon toggling?
For example:
_self.SelectedClients = ko.observableArray([]);
_self.ToggleSelectedUser = function (data, event) {
var toggle = !data.IsSelected();
data.IsSelected(toggle);
if (toggle)
_self.SelectedClients.push(data.ClientName());
else
_self.SelectedClients.remove(data.ClientName());
};
See Fiddle.
Update:
As per your comment, when you do need to walk recursively on the tree, you can try something like this:
function AggregateSelectedClients(clients, results)
{
results = results || [];
if (!clients || !clients.length) return results;
ko.unwrap(clients).forEach(function(v, i)
{
var selected = ko.unwrap(v.IsSelected);
var subClients = ko.unwrap(v.SubClient);
if (selected)
results.push(ko.unwrap(v.ClientName));
if (subClients && subClients.length)
AggregateSelectedClients(subClients, results);
});
return results;
}
The selected children have to be added to the parent selection.
ko.utils.arrayForEach(item.SubClient(), function (subItem) {
if (subItem.IsSelected()) {
selected.push(subItem.ClientName());
}
//cp.GetSelectedClientsArray(subItem);
selected.push.apply(selected, cp.GetSelectedClientsArray(subItem));
});
See fiddle
I hope it helps.

Resources