49

I'm using a UI.Bootstrap accordion and I've defined my heading like so:

<accordion-group ng=repeat="(cname, stations) in byClient">
    <accordion-heading>
        {{ cname }} <span class="pull-right"> {{ Object.keys(stations).length }} Stations</span>
    </accordion-heading>

When that displays the Object.keys(stations).length resolves to nothing. If I put that same length call in my controller I get back the expected count. Is there something preventing the method call from working in AngularJS?

The rest of the accordion that uses stations acts as expected, so I know that it's being populated properly. The byClient data structure basically looks like so:

{
    "Client Name" : {
        "Station Name": [
            {...},
            {...}
        ]
    }
 }
PSL
  • 123,204
  • 21
  • 253
  • 243
Gargoyle
  • 9,590
  • 16
  • 80
  • 145

6 Answers6

88

Yes, That is because Object is a part of window/global and angular cannot evaluate that expression against the scope. When you specify Object.keys in your binding angular tries to evaluate it against the $scope and it does not find it. You could store the reference of object.keys in some utility in rootScope and use it anywhere in the app.

Something like this:-

angular.module('yourApp',[deps...]).run(function($rootScope){
  //Just add a reference to some utility methods in rootscope.
  $rootScope.Utils = {
     keys : Object.keys
  }

  //If you want utility method to be accessed in the isolated Scope 
  //then you would add the method directly to the prototype of rootScope 
  //constructor as shown below in a rough implementation.

  //$rootScope.constructor.prototype.getKeys = Object.keys;

});

and use this as:-

<span class="pull-right"> {{ Utils.keys(stations).length }} Stations</span>

Well this will be available to any child scopes except for isolated scopes. If you are planning to do it on the isolated scope (eg:- Isolated scoped directives) you would need to add the reference of Object.keys on the scope, or as you expose a method on the scope which will return the length.

Or better yet , create a format filter to return the keylength and use it everywhere.

app.filter('keylength', function(){
  return function(input){
    if(!angular.isObject(input)){
      throw Error("Usage of non-objects with keylength filter!!")
    }
    return Object.keys(input).length;
  }
});

and do:-

{{ stations | keylength }}

Demo

PSL
  • 123,204
  • 21
  • 253
  • 243
  • 1
    Just as i would have said it +1 – Matthew.Lothian Aug 14 '14 at 03:47
  • 1
    very nice. You have given two perfect solutions! Thanks :) – Anmol Saraf Dec 30 '14 at 07:27
  • 1
    If you are using an isolate scope, you will need to use `{{ $root.Utils.keys(stations).length }}` – AJ Richardson Oct 28 '15 at 18:28
  • @AJRichardson That is a very good point you made there. What you can do is instead of adding it in the rootScope instance add it to the prototype. i.e `$rootScope.constructor.prototype.getKeys = Object.keys`. It should then be available in the isolate scope as well. Similar to what i did [here](http://stackoverflow.com/questions/25274563/angularjs-communication-between-directives#answer-25274665) – PSL Oct 28 '15 at 18:52
  • why this not exists by default in Angular, is so useful ! – Benjamin Fuentes Feb 26 '16 at 09:31
  • How to use Object.keys in angularjs2. – Akash Rao Mar 10 '16 at 10:34
  • Also I would check for undefined while passing the object as Angular will throw a exception `app.run(function($rootScope){ $rootScope.Utils = { keys : function (o) { return Object.keys(o || {}); } }; })` , btw this is every useful. – Greg Aug 02 '16 at 07:13
4

Use the function to determine the number of object properties:

$scope.keyLength = function (obj) {
    return Object.keys(obj).length;
}

and use:

{{ keyLength(myObj) }}
Alex V
  • 41
  • 1
  • 1
  • 8
2

I think filters are the most AngularJS way of handling structures in template code:

angular.module('app.filters').filter('objectKeysLength', [function() {
    return function(items) {
        return Object.keys(items).length;
    };
}]);

angular.module('app.filters').filter('objectKeys', [function() {
    return function(item) {
        if (!item) return null;
        var keys = Object.keys(item);
        keys.sort();
        return keys;
    };
}]);
jjmontes
  • 24,679
  • 4
  • 39
  • 51
0

In case someone searches for angular 2 and higher solution. It now hat keyvalue pipe, which can be used to interate over objects

Konstantin Vahrushev
  • 1,110
  • 2
  • 11
  • 20
0

I could not get any of the other answers to work in AngularJS 1.6. What worked for me using $window to acccess Object.keys like this $window.Object.keys({ 'a': 1, 'b': 2 })

Cory Koch
  • 470
  • 4
  • 8
0

Here's a solution that worked for me :

export class xyzcomponent{
    Key = Object.keys;
}

Now in the component html file, you can use something like that :

<li *ngFor="let i of Key(stations)">.........</li>
shekhar chander
  • 600
  • 8
  • 14