I lost a couple of hours over this. When creating a directive or attribute name in AngularJS that will be "kebab-cased," do not prefix it with data, such dataFn or dataFromApi. It becomes data-fn or data-from-api and won't work because data- is reserved by javascript for values.

Note: I'm taking a hard line, here. Such a name might work in some cases--but definitely doesn't when trying to invoke an expression passed to an isolate-scope directive.

This will work.

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
</head>
<body ng-app="app">
  <div ng-controller="MyController as vm">
    <button ng-click="vm.alert()">From Body</button>
    <my-directive fn="vm.alert()"></my-directive>
  </div>
</body>
</html>

<script>
  angular.module('app',['MyModule']);

  angular.module('MyModule', [])
    .controller('MyController', MyController)
    .directive('myDirective', myDirective);

  function MyController(){
    var vm = this;
    vm.alert = function(){alert('alert');}
  }

  function myDirective() {
    var directive = {      
      scope: {
        fn: '&'
      },
      template: "<button ng-click='fn()'>From Directive</button>"
    }
    return directive;
  }
</script>

This won't work, because data- is reserved by javascript.

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
</head>
<body ng-app="app">
  <div ng-controller="MyController as vm">
    <button ng-click="vm.alert()">From Body</button>
    <!-- data-fn WILL NOT WORK -->
    <my-directive data-fn="vm.alert()"></my-directive>
  </div>
</body>
</html>

<script>
  angular.module('app',['MyModule']);

  angular.module('MyModule', [])
    .controller('MyController', MyController)
    .directive('myDirective', myDirective);

  function MyController(){
    var vm = this;
    vm.alert = function(){alert('alert');}
  }

  function myDirective() {
    var directive = {      
      scope: {
        dataFn: '&'
      },
      template: "<button ng-click='dataFn()'>From Directive</button>"
    }
    return directive;
  }
</script>

This works, using the non-reserved my- prefix.

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
</head>
<body ng-app="app">
  <div ng-controller="MyController as vm">
    <button ng-click="vm.alert()">From Body</button>
    <!-- my-fn WORKS -->
    <my-directive my-fn="vm.alert()"></my-directive>
  </div>
</body>
</html>

<script>
  angular.module('app',['MyModule']);

  angular.module('MyModule', [])
    .controller('MyController', MyController)
    .directive('myDirective', myDirective);

  function MyController(){
    var vm = this;
    vm.alert = function(){alert('alert');}
  }

  function myDirective() {
    var directive = {      
      scope: {
        myFn: '&'
      },
      template: "<button ng-click='myFn()'>From Directive</button>"
    }
    return directive;
  }
</script>