40

I've created a custom directive which contains a button. This button calls a method from parent scope specified by 'callback' attribute.

<!DOCTYPE html>
<html ng-app="app">
<head>
    <title>Simple directive</title>

    <script src="js/lib/angular/angular.js"></script>

    <script type="text/javascript">
        var app = angular.module('app', []);

        app.controller('TestController', function($scope) {

            $scope.doSomething = function(param) {
                alert('Something called with: ' + param);
            }
        })

        app.directive('myDirective', function() {
            var ret = {
                restrict: 'E',
                scope: {
                    user: '@',
                    callback: '&'       // bound a function from the scope
                },
                template: '<div>Hello {{user}}<button ng-show="hasCallback()" ng-click="callback({userData: user})">Callback</button>',
                controller: function($scope) {
                    $scope.hasCallback2 = function() {
                        var t = typeof $scope.callback;
                        return t == 'function';
                    }

                    $scope.hasCallback = function() {
                        return angular.isDefined($scope.callback);
                    }
                }
            };
            return ret;
        });

    </script>
</head>

<body ng-controller="TestController">

<my-directive user="cat" callback="doSomething(userData)"></my-directive>
<my-directive user="dog" callback="doSomething(userData)"></my-directive>
<my-directive user="pig"></my-directive>

</body>

</html>

My question is:

How can I control visibility of button inside template? I'd like to hide it if callback attribute not specified in custom tag (see 3rd my-directive tag). When I check typeof of callback, I always get 'function' and angular.isDefined(...) also returns true.

Ferenc T
  • 461
  • 1
  • 4
  • 5

2 Answers2

90

Using '&?' returns undefined if the attribute has not been set.

'&' = callback function is defined always.

'&?' = callback function is defined only when attribute is defined in html template.

bindToController: {
    callback: '&?'
},
controller: function() {
    if (this.callback === undefined) {
        // attribute "callback" was not defined
    }
}

Note: Works in Angular 1.4.8. I'm not sure if it works in older versions.

Martin
  • 518
  • 4
  • 7
Bulki S Maslom
  • 1,297
  • 10
  • 5
  • 2
    This worked perfectly and is a much more elegant solution. Should be the correct answer. – Rebecca Mar 22 '16 at 03:58
  • There is not a single word about it in [the docs](https://docs.angularjs.org/api/ng/service/$compile#-scope-). – fracz Jun 14 '16 at 16:38
  • 3
    This does not work in 1.3.18, it always wires in a wrapper function no matter if it is defined in the DOM or not. – noherczeg Jun 29 '16 at 13:57
  • Docs: https://docs.angularjs.org/api/ng/service/$compile Directive Definition Object -> scope section – vip Apr 04 '17 at 11:30
44

Looking at angularjs source code, I see this:

case '&':
    parentGet = $parse(attrs[attrName]);
    isolateScope[scopeName] = function(locals) {
         return parentGet(scope, locals);
    };
    break;

The parentGet is the bound function expression. Unfortunately, this is a local variable which is only available to the function assigned to isolateScope[scopeName] via closure.

Instead of trying to find a way to get that variable, a simple solution is just to check the attrs. Try:

link: function(scope,elem,attrs) {

      scope.hasCallback = function() {
        return angular.isDefined(attrs.callback);
      }
    }

DEMO

Khanh TO
  • 46,783
  • 12
  • 97
  • 112
  • helped me, but instead of `angular.isDefined` I prefer vanilla-js `attrs.hasOwnProperty('callback');` – Stalinko May 08 '17 at 12:55
  • @Stalinko: if you want to use vanila-js for some reason, a simple check for `undefined` should work: `if (typeof attrs.callback === "undefined")` – Khanh TO May 10 '17 at 13:24