How to check for an empty object in an AngularJS view - javascript

In the controller's scope I have something like:
$scope.card = {};
In the view I must check if my object is still an empty literal, {}, or if it contains some data in some fields.
I tried this:
ng-show="angular.equals({}, card)"
and
ng-show="!angular.equals({}, card)"
but it didn't work.
Are there any better ways? How do you check if an object is empty or if it contains some fields?

You can use: Object.keys(card).length === 0
But make sure you use it from a method of the controller as the Object is not available in the view, like:
$scope.isObjectEmpty = function(card){
return Object.keys(card).length === 0;
}
Then you can call the function from the view:
ng-show="!isObjectEmpty(card)"

Use json filter and compare with '{}' string.
<div>
My object is {{ (myObject | json) == '{}' ? 'Empty' : 'Not Empty' }}
</div>
ng-show example:
<div ng-show="(myObject | json) != '{}'"></div>

Try this:
angular.equals({}, $scope.card)

Create a function that checks whether the object is empty:
$scope.isEmpty = function(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return true;
};
Then, you can check like so in your html:
ng-show="!isEmpty(card)"

http://plnkr.co/edit/u3xZFRKYCUh4D6hGzERw?p=preview
Because angular is not available from the scope, you can pass it to your controller scope.
$scope.angular = angular;

please try this way with 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>
Via from : Checking if object is empty, works with ng-show but not from controller?

This will do the object empty checking:
<div ng-if="isEmpty(card)">Object Empty!</div>
$scope.isEmpty = function(obj){
return Object.keys(obj).length == 0;
}

You should not initialize your variable to an empty object, but let it be undefined or null until the conditions exist where it should have a non-null/undefined value:
$scope.card;
if (someCondition = true) {
$scope.card = {};
}
Then your template:
ng-show="card"

You can use plain javascript Object class to achieve it,
Object class has keys function which takes 1 argument as input as follows,
Object.keys(obj).length === 0
You can achieve it in 3 ways,
1) Current controller scope
2) Filter
3) $rootScope
1) First way is current controller scope,
$scope.isObjEmpty = function(obj){
return Object.keys(obj).length === 0; }
Then you can call the function from the view:
ng-show="!isObjEmpty(obj)" if you want to show and hide dom dynamically &
ng-if="!isObjEmpty(obj)" if you want to remove or add dom dynamically.
2) The second way is a custom filter. Following code should work for the object & Array,
angular.module('angularApp')
.filter('isEmpty', [function () {
return function (obj) {
if (obj == undefined || obj == null || obj == '')
return true;
if (angular.isObject(obj))
return Object.keys(obj).length != 0;
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
return false;
}
}
return true;
};
}]);
<div ng-hide="items | isEmpty"> Your content's goes here </div>
3) The third way is $rootScope,
create a plain javascript function and add it in $rootScope server it will accessible default in all scopes and UI.
function isObjEmpty (obj){
return Object.keys(obj).length === 0; }
$rootScope.isObjEmpty = isObjEmpty ;

Create a $scope function that check it and returns true or false, like a "isEmpty" function and use in your view on ng-if statement like
ng-if="isEmpty(object)"

I have met a similar problem when checking emptiness in a component. In this case, the controller must define a method that actually performs the test and the view uses it:
function FormNumericFieldController(/*$scope, $element, $attrs*/ ) {
var ctrl = this;
ctrl.isEmptyObject = function(object) {
return angular.equals(object, {});
}
}
<!-- any validation error will trigger an error highlight -->
<span ng-class="{'has-error': !$ctrl.isEmptyObject($ctrl.formFieldErrorRef) }">
<!-- validated control here -->
</span>

I had to validate an empty object check as below
ex:
<div data-ng-include="'/xx/xx/xx/regtabs.html'" data-ng-if =
"$parent.$eval((errors | json) != '{}')" >
</div>
The error is my scope object, it is being defined in my controller as $scope.error = {};

A good and effective way is to use a "json pipe" like the following in your HTML file:
<pre>{{ yourObject | json }}</pre>
which allows you to see clearly if the object is empty or not.
I tried quite a few ways that are showed here, but none of them worked.

If you plan on using a lot of the methods and properties of Object in your Angular JS app, why not use:
angular.module('core').run(function ($rootScope) {
$rootScope.Object = window.Object;
});
Then in your view, you could do something like:
<div ng-if="Object.keys(fooBar).length > 0"></div>

You have to just check that the object is null or not. AngularJs provide inbuilt directive ng-if. An example is given below.
<tr ng-repeat="key in object" ng-if="object != 'null'" >
<td>{{object.key}}</td>
<td>{{object.key}}</td>
</tr>

Related

How to create random/shuffle filter without infinite digest loop

I would want to achieve the following:
To be able to show image from array in random order.
To solve this, I decided to create a filter :
var app = angular.module('app');
app. filter('randomizer', randomizer);
function randomizer() {
return function (collection, defaultValue) {
var result = defaultValue;
if (!!collection) {
if (Array.isArray(collection) && collection.length === 1) {
result = collection[0] || defaultValue;
} else if (!Array.isArray(collection)) {
result = collection || defaultValue;
} else {
// randomize here please
var idx = parseInt(((Math.random()) * collection.length));
result = collection[idx] || defaultValue;
}
}
return result;
}
}
Now in template, I am calling as:
<div class="masonry-pin masonry-brick" ng-repeat="p in vm.list | orderBy: 'updatedAt':true">
<img ng-src="{{vm.baseUrl + 'documents/view/' + ( p.documents | randomizer:{id: 'notfound'}:p.id).id }}">
</div>
However, I am getting this error:
Uncaught Error: [NG-Modular Error] [$rootScope:infdig] 10 $digest()
iterations reached. Aborting! Watchers fired in the last 5 iterations:
[[{"msg":"fn:
expressionInputsWatch","newVal":"http://localhost:3002/documents/view/158","oldVal":"http://localhost:3002/documents/view/159"},{"msg":"fn:
After doing some research, I found that it is do with value changes, but where I am changing my values.
Now I can understand that some how it might be the scope is changing. However, what I have noticed that even if I have a simple filter like this: {{p.documents | console}}
and the console filter just takes an array and print in the console.
Now when I have 10 objects in the list, It is calling filter 30 times.
Here is how the console filter is, but for 10 items it is calling 66 times.
Why ??
app.filter('console', pipeConsole);
function pipeConsole() {
return function (value, o) {
print(value);
print(o);
print(count);
}
function print(o) {
if (!!o) {
console.log(o);
}
else if (o === null) {
console.warn(o);
}
}
};
Here I am not even returning different values... (if I go with the logic explained here - Angular: infinite digest loop in filter)
Even if it is not a filter,then also it is causing this problem
Now that I have created a service function, and I am having this problem.
<img ng-src="{{vm.baseUrl + 'documents/view/' + vm.random( p.documents , {id: 'notfound'}).id }}">
vm.random = function (a, s) {
return utility.randomizer(a, s);
};
So what is the solution??
To fix the infinite digest, you need to wrap your filter in a memoize function. The best way to solve your problem is to install knuth-shuffle and lodash from npm, then use whatever module system you like to create the filter. This example is with CommonJS/browserify.
var memoize = require('lodash/function/memoize');
var shuffle = require('knuth-shuffle');
app.filter('shuffle', function() {
return memoize(shuffle);
});
When creating filters this way, you may have a problem if an empty value is passed to the shuffle function. In that case, just add a check:
app.filter('shuffle', function() {
return memoize(function(input) {
if (input === undefined) { return; }
return shuffle.apply(null, arguments);
});
});
You can learn more about the infinite digest problem from this answer.
To re-shuffle the list, you can pass an arbitrary $scope property to the filter, then change that property when you want to re-shuffle. Either incrementing a number or using Math.random() are a good way to do this. This works because the result is cached according to the arguments passed, so passing an otherwise useless argument produces a new result.
myList | shuffle:whatever
$scope.whatever = 0;
$scope.reshuffle = function() {
++$scope.whatever;
// OR
$scope.whatever = Math.random();
};

Replace value with API value in ng-repeat

I am listing a bunch of JSON objects to a view, but only it's category ID is included. Not the name, which I need to display. I have to make a separate $http call to process which items match. Values will not render to view. Code:
$scope.cardCategories = function(id) {
angular.forEach($scope.categoryFilter, function(category, index){
if (category.id == id) {
//console.log(category.name);
return category.name;
} else {
return;
}
})
}
value inside a simplified ng-repeat
<div ng-repeat="topic in latest">
{{cardCategories(topic.category_id)}}
</div>
I have also attempted to write this as a filter, but the value will not display. Console shows the matches. My suspicion is that I have to process the original latest array. Thanks.
.filter('cardCategories', function($rootScope, $state){
return function(catId) {
if (catId !== undefined && $rootScope.cardCategories) {
angular.forEach($rootScope.cardCategories.categories, function(category, index){
if (category.id == catId.category_id) {
return category.name;
} else {
return;
}
})
}
}
})
View:
{{topic.category_id | cardCategories}}
That return statement in the forEach callback function will not return a value from the $scope.cardCategories, it returns something from the callback you provided to forEach (and angular happily ignores it). Try something like this:
$scope.cardCategories = function(id) {
var i = $scope.categoryFilter.length; // assuming it's an array
while(i--) {
if($scope.categoryFilter[i].id === id) {
return $scope.categoryFilter[i].name;
}
}
};
Also -- there's no way to break (stop early) an angular.forEach loop, so for performance reasons it's better to avoid it if you're locating something inside an array/object.
You need to make sure the forEach loop doesn't do a return if the current iteration doesn't match the ID you passed in.
Take out the else condition from your if statement
$scope.cardCategories = function(id) {
angular.forEach($scope.categoryFilter, function(category, index){
if (category.id == id) {
return category.name;
}
});
};

Returning an array containing named properties of each object

Just getting stumped and not sure why my code isn't working. The instructions are to take an array of objects and a property name, and return an array containing the named property of each object.
so something likepluck([{a:1}, {a:2}], 'a') // -> [1,2] where pluck is the function I want to create.
So far, I have:
function pluck(objs, name) {
var pushedArray=[];
for (i=0;i<objs.length;i++){
var totalpushedArray = pushedArray.push(name[i]);
}
}
but the code itself isn't working as far as I can tell. There are additional guidelines to leave undefined if the object doesnt have the property but I figure that I will get to that after I solve this first.
You forgot to add a return statement and you're not referencing the object property. See below.
function pluck(objs, name) {
var pushedArray = [];
for (var i = 0; i < objs.length; i++) {
pushedArray.push(objs[i][name]);
}
return pushedArray;
};
If you want a more "functional" solution, you can use map.
function pluck(objs, name) {
return objs.map(function(obj) {
return (obj.hasOwnProperty(name) ? obj[name] : null);
});
};

Knockout: Change css class based on value of observable

I use foreach on an observable array:
<div id="mainRight" data-bind="foreach: notifications">
<div class="statusRow">
<div class="leftStatusCell">
<div class="leftStatusCellColor" data-bind="css: availabilityCssClass($data.availability)"></div>
</div>
<div class="topRightStatusCell" data-bind="text: sip"></div>
<div class="bottomtRightStatusCell ellipsisSingleline" data-bind="text: note"></div>
</div>
</div> <!== end mainRight ==>
As you can see, I pass the current value of availability to the function availabilityCssClass, which compares the value to some predefined strings. Depending on the matching string, it returns a class name.
self.availabilityCssClass = ko.computed(function (value) {
var availability = value;
if (availability === "Busy" || "DoNotDisturb" || "BeRightBack")
return "leftStatusCellColorOrange";
else if (availability === "Away" || "Offline")
return "leftStatusCellColorRed";
else
return "leftStatusCellColorGreen";
});
This is my model. The data comes from an external data source.
function Notification(root, sip, availability, note) {
var self = this;
self.sip = ko.observable(sip);
self.availability = ko.observable(availability);
self.note = ko.observable(note);
};
self.notifications = ko.observableArray();
However, it doesnt work as is. When the computed function is not commented out, the foreach does not iterate over the data and the div is empty. But I can see that the viewModel is not empty.
You cannot pass value into computed in such way. It is better to add this computed to Notification view model and use self.availability property:
function Notification(root, sip, availability, note) {
var self = this;
self.sip = ko.observable(sip);
self.availability = ko.observable(availability);
self.note = ko.observable(note);
self.availabilityCssClass = ko.computed(function() {
var availability = self.availability();
if (["Busy", "DoNotDisturb", "BeRightBack"].indexOf(availability) != -1) return "leftStatusCellColorOrange";
else if (["Away", "Offline"].indexOf(availability) != -1) return "leftStatusCellColorRed";
else return "leftStatusCellColorGreen";
});
};
Your if statement wasn't correct, so I fixed the logic. Here is working fiddle: http://jsfiddle.net/vyshniakov/Jk7Fd/
You just need to make availabilityCssClass a function. As you've written it, it's not a computed observable since it has no observable dependencies.
self.availabilityCssClass = function (value) {
var availability = value;
if (availability === "Busy" || "DoNotDisturb" || "BeRightBack")
return "leftStatusCellColorOrange";
else if (availability === "Away" || "Offline")
return "leftStatusCellColorRed";
else
return "leftStatusCellColorGreen";
};
The CSS binding wants a object literal with the name of the CSS class as member name and the value true or false depeding on you want to remove or add the class
data-bind="css: { 'css-class-name': true }"
edit: Hmm, they have changed the css binding in 2.2 ;)

Getting null or not an object error in javascript code

Here is the line which is causing null or not an object error
if(frm.elements["hdn_retain"+indexval].value==""){
....
} else {
....
}
frm.elements["hdn_retain"+indexval] may be a null object. So, it will have error when getting the value. You can check the frm.elements["hdn_retain"+indexval] if it is null first.
if(frm.elements["hdn_retain"+indexval] != null && frm.elements["hdn_retain"+indexval].value=="")
Either frm or frm.elements["hdn_retain"+indexval] isn't a valid object (doesn't exist in the dom) and therefore you can't access it's property.
you could try something like:
if(frm.elements["hdn_retain"+indexval] && frm.elements["hdn_retain"+indexval].value==""){
Following is the result of alert statement:
alert("frm:::"+frm);
alert("frm elements::::"+frm.elements);
alert("frm hdn_retain :: "+frm.elements["hdn_retain"+indexval]);
frm:::[object]
frm elements::::[object]
frm hdn_retain :: undefined
you can use this utility method getProperty i always use to make sure i get a nested namespace back without worrying about whether or not something is defined:
function getProperty(ns, obj) {
var nsArray = ns.split('.'),
i = 0,
nsLen = nsArray.length;
while (nsLen > 0) {
var newNs = nsArray.shift();
if (obj[newNs]) {
obj = obj[newNs];
} else {
return false;
}
nsLen = nsArray.length;
}
return obj;
};
var index = "hdn_retain" + indexval;
// the following `value` will come back as a valid object/value or a false
value = getProperty('elements.' + index + '.value', frm);
if (value) {
// do whatever
} else {
// do not whatever
}
this can be applied not only to this situation but to any other situation you need to make sure a certain namespace is available before usage.

Categories

Resources