How to hide element if transcluded contents are empty?
Asked Answered
S

6

11

I created a very simple directive which displays a key/value pair. I would like to be able to automatically hide the element if the transcluded content is empty (either zero length or just whitespace).

I cannot figure out how to access the content that gets transcluded from within a directive.

app.directive('pair', function($compile) {
  return {
    replace: true,
    restrict: 'E',
    scope: {
      label: '@'
    },
    transclude: true,
    template: "<div><span>{{label}}</span><span ng-transclude></span></div>"
  }
});

For example, I would like the following element to be displayed.

<pair label="My Label">Hi there</pair>

But the next two elements should be hidden because they don't contain any text content.

<pair label="My Label"></pair>
<pair label="My Label"><i></i></pair>

I am new to Angular so there may be a great way handle this sort of thing out of the box. Any help is appreciated.

Sambar answered 2/4, 2013 at 15:49 Comment(0)
M
9

Here's an approach using ng-show on the template and within compile transcludeFn checking if transcluded html has text length.

If no text length ng-show is set to hide

app.directive('pair', function($timeout) {
  return {
    replace: true,
    restrict: 'E',
    scope: {
      label: '@'
    },
    transclude: true,
    template: "<div ng-show='1'><span>{{label}} </span><span ng-transclude></span></div>",
    compile: function(elem, attrs, transcludeFn) {
            transcludeFn(elem, function(clone) { 
              /* clone is element containing html that will be transcludded*/
               var show=clone.text().length?'1':'0'
                attrs.ngShow=show;
            });
        }
  }
});

Plunker demo

Militant answered 2/4, 2013 at 17:20 Comment(2)
+1 for any answer that shows a use case for the esoteric transcludeFn :).Salts
in the transcludeFn, clone.text().trim().length would be a bit more accurateEnlace
D
6

Maybe a bit late but you can also consider using the CSS Pseudo class :empty. So, this will work (IE9+)

.trancluded-item:empty {
  display: none;
}

The element will still be registered in the dom but will be empty and invisible.

Dentation answered 22/5, 2017 at 1:41 Comment(1)
This is very elegant and works for non angularjs applications as well.Lucie
S
1

The previously provided answers were helpful but didn't solve my situation perfectly, so I came up with a different solution by creating a separate directive.

Create an attribute-based directive (i.e. restrict: 'A') that simply checks to see if there is any text on all the element's child nodes.

function hideEmpty() {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            let hasText = false;

            // Only checks 1 level deep; can be optimized
            element.children().forEach((child) => {
                hasText = hasText || !!child.text().trim().length;
            });

            if (!hasText) {
                element.attr('style', 'display: none;');
            }
        }
    };
 }

angular
    .module('directives.hideEmpty', [])
    .directive('hideEmpty', hideEmpty);

If you only want to check the main element:

link: function (scope, element, attr) {
    if (!element.text().trim().length) {
        element.attr('style', 'display: none;');
    }
}

To solve my problem, all I needed was to check if there were any child nodes:

link: function (scope, element, attr) {
    if (!element.children().length) {
        element.attr('style', 'display: none;');
    }
}

YMMV

Sclerous answered 3/12, 2015 at 18:43 Comment(0)
O
1

If you don't want to use ng-show every time, you can create a directive to do it automatically:

.directive('hideEmpty', ['$timeout', function($timeout) {

    return {
        restrict: 'A',

        link: {
            post: function (scope, elem, attrs) {
                $timeout(function() {
                    if (!elem.html().trim().length) {
                        elem.hide();
                    }
                });
            }
        }
    };

}]);

Then you can apply it on any element. In your case it would be:

<span hide-empty>{{label}}</span>
Overside answered 1/6, 2016 at 11:19 Comment(0)
H
0

I am not terribly familiar with transclude so not sure if it helps or not.

but one way to check for empty contents inside the directive code is to use iElement.text() or iElement.context object and then hide it.

Holmberg answered 2/4, 2013 at 16:4 Comment(1)
Thanks. I actually tried doing that within the link function, but the contents had not yet been transcluded by that point.Sambar
H
0

I did it like this, using controllerAs.

/* inside directive */

         controllerAs: "my",
controller: function ($scope, $element, $attrs, $transclude) {
//whatever controller does
},
         compile: function(elem, attrs, transcludeFn) {
                    var self = this;
                    transcludeFn(elem, function(clone) {
                        /* clone is element containing html that will be transcluded*/
                        var showTransclude = clone.text().trim().length ? true : false;
                        /* I set a property on my controller's prototype indicating whether or not to show the div that is ng-transclude in my template */
                        self.controller.prototype.showTransclude = showTransclude;
                    });
                }

/* inside template */

<div ng-if="my.showTransclude" ng-transclude class="tilegroup-header-trans"></div>
Heptangular answered 8/10, 2014 at 20:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.