Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I access Object.keys(object).length from a directive template?

I'm trying to create a directive that will list all the errors contained in an errors object ({phone: ["is required"]}, but only if the object is non-empty. (It doesn't make sense to say "The following errors…" when there were none.)

I figured out how to check if an object is empty by testing Object.keys(errors).length. The problem is that I can't figure out how to access Object.keys from my directive template. Is this possible?

Since Angular expressions are "evaluated" (using $parse, not eval() ) in the context of a scope instead of in the context of window, we don't have access to things like Object from the directives template, because Object isn't a property of the scope. (Docs: Expressions)

Makes sense so far. It goes on to say that "Unlike JavaScript, where names default to global window properties, Angular expressions must use $window explicitly to refer to the global window object. For example, if you want to call alert() in an expression you must use $window.alert().

But I can't seem to access Object even if I do $window.Object. What am I missing?


Here's the code for the directive I'm debugging (and here's a jsfiddle):

app.js.coffee:

…
.directive 'displayErrorsAllKeys', ->
  {
    restrict: 'A',
    scope: {
      errors: '='
      debug: '@debug'
    }
    templateUrl: 'templates/display-errors-all-keys'
  }

.run(['$rootScope', ($rootScope) ->
  $rootScope.nonEmpty = (object) ->
    !! Object.keys(object).length
])

.controller('TestDisplayErrorsAllKeys',
['$scope',
( $scope ) ->

  $scope.other_errors = {}

  $scope.add_errors = ->
    $scope.other_errors = {"surname":["is required"],"phone":["is required"]}

  $scope.clear_errors = ->
    $scope.other_errors = {}
])

display-errors-all-keys.ngt.haml:

.errors(ng-show="$window.Object.keys(errors).length > 0")
  %p The following errors prevented this from saving:
  %div(ng-repeat="(key, error_messages) in errors") {{key}}: {{error_messages | toSentence}}

test_ng_display-errors-all-keys.html.haml

:scss
  .errors {
    color: red;
  }

%div(ng-app='MyApp')
  %form(ng-controller="TestDisplayErrorsAllKeys")
    %p errors: {{ other_errors }}
    %p nonEmpty(other_errors): {{ nonEmpty(other_errors) }}
    %hr/

    %div(display-errors-all-keys errors="other_errors")

    %input(type="button" value="Add errors" ng-click="add_errors()")/
    %input(type="button" value="Clear errors" ng-click="clear_errors()")/

I finally got it to work by defining a helper method in my scope and calling that instead ($root.nonEmpty(errors)) (see jsfiddle for working demo).

This is probably a pretty good solution, but:

  1. Is there an even better way to solve this? How would you have done it (written the ng-show expression)?

  2. How would I get it to work using Object.keys(errors).length directly in the ng-show??

like image 425
Tyler Rick Avatar asked Apr 04 '14 02:04

Tyler Rick


People also ask

How to find object key length in JavaScript?

Method 1: Using the Object. keys() method is used to return the object property name as an array. The length property is used to get the number of keys present in the object. It gives the length of the object.

What is object keys in Javascript?

Object.keys() returns an array whose elements are strings corresponding to the enumerable properties found directly upon object . The ordering of the properties is the same as that given by looping over the properties of the object manually.


2 Answers

I would provide the helper function in the directives scope (which is isolated). Typically by providing a link function for the directive:

.directive('displayErrorsAllKeys', function() {
    return {
        restrict: 'A',
        scope: {
            errors: '=',
            debug: '@debug'
        },
        link: function (scope) {
            scope.errorsExists = function(object) {
                return object && Object.keys(object).length;
            };
        },
        templateUrl: 'templates/display-errors-all-keys'
    };
})

Placing logic and data in the root scope is seldom a good practice. Also note that I named the function errorExists, which provides som abstraction upon the actual representation of the errors.

As for your second question, you could add scope.Object = Object; in your link function, but DON'T! Such specific logic doesn't belong in your template. The template should be concerned with wether or not to show the errors, but not why.

like image 193
Jon Avatar answered Nov 02 '22 05:11

Jon


Here's a nonEmpty filter I added to solve this problem (JavaScript equivalent)

.filter 'nonEmpty', ->
  (object) ->
    !! (object && Object.keys(object).length > 0)

Tests:

%div {{ null | nonEmpty }} should be false
%div {{ { } | nonEmpty }} should be false
%div {{ {a: '1'} | nonEmpty }} should be true

My ng-show condition in the example above can be simplified to just this:

ng-show="errors | nonEmpty"

Now I can easily reuse this check logic, without having to add a new method to each directive or controller where I may want to use it.

like image 35
Tyler Rick Avatar answered Nov 02 '22 05:11

Tyler Rick