Question

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.

Was it helpful?

Solution

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

OTHER TIPS

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.

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

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>

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.

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>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top