2

I have a directive for a popup bubble, where the buttons to be displayed in the popup are provided in an attribute as an array of objects:

JS (Controller)

$scope.buttons = [{ label: 'OK' }, { label: 'Cancel' }]

The directive looks like this:

HTML

<ui-popup buttons="buttons"></ui-popup>

JS (Directive)

'use strict';

angular
    .module('ui')
    .directive('uiPopup', [function () {

        return {
            replace: true,
            restrict: 'E',
            transclude: true,
            templateUrl: '/uikit/views/ui-popup.html',
            scope: {
                buttons: '=',
                anchor: '@',
                x: '@',
                y: '@'
            },
            link: function ($scope, element, attrs, ngModel) {
                ...
            }
        };
    }]);

This works fine. However, what I need is to start with no buttons in the bubble, then add the buttons when an event in the application occurs. So I start with an empty array, and use $scope.buttons.push(obj) to add each button. Note I'm maintaining the original array, not replacing the array, so data-binding shouldn't be broken. However, the new button doesn't show up in the bubble, and debugging shows the button isn't added in the directive scope.

After experimenting, I found by chance that if I start with a non-empty array and then add it works just fine. Why does angular data-binding break on an empty array? What can I do to work around the issue?

Edit

The event is called on ng-click and looks like this:

JS (Controller)

$scope.onClick = function () {

    $scope.locked = true;
    $scope.buttons.push({ label: 'OK' });
};
8
  • 1
    How are you binding to events? You might need to use $scope.$apply() in your event handler. Commented Nov 5, 2014 at 17:47
  • Good suggestion @AnthonyChu. It's from an ng-click so an $apply() block shouldn't be the problem. Commented Nov 5, 2014 at 17:49
  • This is odd that the original length of the array matters. Can you recreate in a fiddle? Distilled my understanding of your problem to this but it seems to work regardless if array is originally empty or not... jsfiddle.net/0xnotggr Commented Nov 5, 2014 at 18:04
  • jsfiddle.net/dizel3d/t0qagtc1 It looks like your example? Commented Nov 5, 2014 at 18:18
  • The only theory I had was that an empty arrays might have some memory voodoo going on the prevented binding from working correctly. Bit of a stretch though. Commented Nov 5, 2014 at 18:19

1 Answer 1

1

The directive HTML looked like this:

...
<div class="buttons" ng-if="buttons">
    <ui-button color="primary" size="small" ng-repeat="button in buttons">{{ button.label }}</ui-button>
</div>
...

You can see the buttons in the popup are only shown if buttons is truthy. Interestingly, the div.buttons wasn't showing when buttons was empty, even though !![] gives true in javascript, so I can only assume Angular uses its own equality function to test here. However, even when a button was subsequently added to the array, the div wasn't showing.

When I changed the equality to ng-if="buttons.length", (as it probably should have been from the start) things worked correctly.

Sign up to request clarification or add additional context in comments.

3 Comments

Took a quick look at angular's source... It looks like you're right that it uses a custom toBoolean() function in 1.2.x to determine truthiness in ngIf; and an empty array is considered falsy. Because ngIf simply puts a watch on its expression, it's just watching the reference to the array and doesn't detect a change when the array's content changes. What that meant for you is that ngIf evaluated to false with an empty array, and never changed to true when you pushed stuff into it. Looks like the behavior is changed in 1.3.0; an empty array is now truthy in ngIf.
In case you're interested here's the commit that made the change... github.com/angular/angular.js/commit/…
Ah, great find! Mystery solved, and a good insight into the ng-if directive. Thanks for the help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.