2

I'm building a directive that takes the form of a card. When I load the card template I want to ensure that the card is hidden if a user has previously hid the card. I can track this via the request.hide attribute.

Here is my directive (barebones):

app.directive('request', ['$http', '$timeout', function($http, $timeout) {
    return {
        replace: true,
        templateUrl: '/assets/request.html',
        transclude: false,
        scope: {
            request: '='
        },
        controller: ['$scope', '$http', '$timeout', function($scope, $http, $timeout) {
            // Cool stuff goes in here
        }]
    };
}]);

And here is my template:

<div>
    <div ng-hide"request.hide" class="card">
        <!-- Cool stuff goes in here -->
    </div>
</div>

When the page loads, each request has a hide attribute. In theory, when I call my directive, it should be hidden if hide === true. Unfortunately this doesn't seem to be the case. No matter what I try, I cannot get my directive to be hidden when it's initialized. I've tried using ng-hide="request.hide", ng-show="!request.hide", and ng-if="!request.hide" on the root element, but nothing works.

I wondered to myself if these directives don't work on the root element of a custom directive, so I tried wrapping my directive in an additional div and using ng-hide, ng-show, or ng-if on the .card div, which is now a child element, but that had no effect either.

It seems as though ng-hide is either not being evaluated at all, or is being evaluated before request is defined on the directive's scope.

Daniel Bonnell
  • 4,817
  • 9
  • 48
  • 88

4 Answers4

1

There's a small typo in the template markup you provided, missing an = sign after ng-hide. But I'm guessing that's just a typo while you were writing up the question.

Otherwise, the directive code looks fine, and it should work. You should double check the "request" object that you're binding the directive to, and make sure that the hide property is actually a boolean value, and not a string.

ng-if, ng-show, and ng-hide all set up watchers, so the issue shouldn't be that the expression gets evaluated before the scope is populated.

Just for testing purposes, try setting up a boolean on your scope in your directives controller, and do the hide or if against that.

cloudberry
  • 379
  • 3
  • 10
  • I tried that and it had no effect. I tried tying `ng-show` and `ng-if` to `hide`, where `$scope.hide = $scope.request.hide`. I also checked to ensure that `request.hide` is a boolean, not a string. If I bind to `hide` instead of `request.hide`, all of my directives are hidden, instead of just the ones for which `hide === true`. – Daniel Bonnell Mar 09 '16 at 15:43
  • 1
    Here's a plunker example of a directive that hides its content based on the 'request' object passed to it. Just toggle the value in the controller from true to false to see the directive showing and hiding https://plnkr.co/edit/3WtwSIBtDYPQ9QkRecrO?p=preview – cloudberry Mar 10 '16 at 07:10
  • Your directive does what its supposed to, but I wonder if the problem lies in the fact that my directive asynchronously loads the template, while your directive's template is available on page load. In my code I already tried binding `hg-hide`/`ng-show`/`ng-if` to `$scope.hide`, which I then define on the first line of my controller, but the it made no difference. When I wrapped the definition in a `$timeout`, it worked, which seems to indicate to me that my expression wasn't being evaluated the first time. – Daniel Bonnell Mar 10 '16 at 14:21
  • 1
    Hi, I updated the plunker to resemble your situation a bit closer. Both the template and request data are being loaded asynchronously. Still, the directive's template is bound directly to it's scope, and you can see that it hides (though there is a flash while the data request is being made.. ng-cloak is for this flashing issue). The directive doesnt even try to render before its template is resolved, and when the parent controller updates its data, the change propagates down to the directive. Could you try it out? https://plnkr.co/edit/cCELNPLIEyHVJmIN0lbc?p=preview – cloudberry Mar 10 '16 at 20:52
  • This pluncker works correctly as well, but I've still been unable to get it to work in my app without the `$timeout` block. There must be some other confounding factor I'm missing that's not represented here. It's difficult to say for sure because my app is so complex. In any case, now that I got a hacky fix working with `$timeout` I've pretty much given up on solving the root issue. – Daniel Bonnell Mar 10 '16 at 21:15
0

Try using ngCloak on the wrapper-div. It should hide the div and its children as long as angular is still evaluating things.

https://docs.angularjs.org/api/ng/directive/ngCloak

edit: Actually, no. ngCloak doesn't work like I remembered. It hides the element and all its children, but only as long as it wasn't encountered in the compile phase - which is too early for my liking. You could write your own cloaking directive, though.

Here is ngCloak's source:

var ngCloakDirective = ngDirective({
  compile: function(element, attr) {
    attr.$set('ngCloak', undefined);
    element.removeClass('ng-cloak');
  }
});

You would need to mimic its behavior in the pre-/post-link phase.

JanS
  • 2,065
  • 3
  • 27
  • 29
  • I don't think I have nearly a strong enough understanding of the pre/post link phase to write my own version of `ngCloak`. I tried it out of the box to see if it would help, but it didn't change anything for me. I'm convinced that the root of the problem is that the directive is not evaluating the `ng-show`/`ng-hide`/`ng-if` on compile. – Daniel Bonnell Mar 09 '16 at 15:50
0

ng-show can be unreliable.

<div>
    <div ng-show"!!request.hide" class="card">
        <!-- Cool stuff goes in here -->
    </div>
</div>
Thomas Bormans
  • 5,156
  • 6
  • 34
  • 51
Kentonbmax
  • 938
  • 1
  • 10
  • 16
-1

I finally came up with a solution to my problem, though I'd still like to know what caused the problem and the best way to address it.

I had a theory that ng-show/ng-hide/ng-if were not being evaluated when my directive was compiled, so I decided to use ng-hide="hide" on my directive's root element as before, but in my controller, rather than immediately set $scope.hide = $scope.request.hide, I decided to move this within a $timeout block with a delay of 10ms, like so:

$timeout(function() {
    $scope.hide = $scope.request.hide;
}, 10)

This triggers a digest cycle after the directive is compiled, causing ng-hide to be reevaluated. The delay is long enough for the directive to compile but short enough to be imperceptible to the user.

Daniel Bonnell
  • 4,817
  • 9
  • 48
  • 88
  • You really don't need to be doing this digest cycle hack with ng-show, ng-hide, and ng-if. As I said above, these three directives setup watchers on their given expressions. You should be able to bind ng-hide directly from your directive's isolate scope to the view. Check out the plunker I provided above. – cloudberry Mar 10 '16 at 07:15