0

I ran into a problem adapting the solution from How to expose behavior from a directive with isolated scope?. I wanted to expose my directive as an Element rather than as an Attribute:

Here's a JSFiddle. When you click the first button, which uses the Attribute approach, everything is ok. But the second button uses the Element approach and it gives an error.

Here is the code as well:

HTML:

<div ng-app="main">
    <div ng-controller="MyCtrl">Click the first button and everything is ok:
        <br>
        <button ng-click="callFnInAttribute()">Call Function in Attribute Directive</button>
        <br>{{errorViaAttribute}}
        <div my-attribute my-fn-via-attribute="fnInCtrlViaAttribute"></div>
        <br>
        <br>But click the second button and you get an error:
        <br>
        <button ng-click="callFnInElement()">Call Function in Element Directive</button>
        <br>{{errorViaElement}}
        <my-element my-fn-via-element="fnInCtrlViaElement"></my-element>
        <br>
        <br>The only difference is the type of directive used. Why does it work with an Attribute type of directive but not with an Element directive?</div>
</div>

JavaScript:

angular.module("main", []).controller("MyCtrl", function ($scope) {
    $scope.callFnInAttribute = function () {
        try {
            $scope.fnInCtrlViaAttribute();
            $scope.errorViaAttribute = "OK";
        } catch (anError) {
            $scope.errorViaAttribute = "Error: " + anError;
        }
    };

    $scope.callFnInElement = function () {
        try {
            $scope.fnInCtrlViaElement();
            $scope.errorViaElement = "OK";
        } catch (anError) {
            $scope.errorViaElement = "Error: " + anError;
        }
    };
}).directive("myAttribute", function () {
    return {
        require: 'A',
        scope: {
            myFnViaAttribute: '='
        },
        controllerAs: 'chartCtrl',
        bindToController: true,
        controller: function ($scope) {
            $scope.myFnViaAttribute = function () {
                console.log("myFnViaAttribute called");
            }
        }
    };
}).directive("myElement", function () {
    return {
        require: 'E',
        scope: {
            myFnViaElement: '='
        },
        controllerAs: 'chartCtrl',
        bindToController: true,
        controller: function ($scope) {
            $scope.myFnViaElement = function () {
                console.log("myFnViaElement called");
            }
        }
    };
});

This is using the following AngularJS version: https://code.angularjs.org/1.1.0/angular.min.js

How do I correctly expose the behavior from an Element?

Community
  • 1
  • 1
Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113

1 Answers1

1

I think your error simply comes from the fact that you wrote require instead of restrict in your directives. require is to make sure another directive is present in the same element, restrict is to define the HTML structure of your directive.

.directive("myAttribute", function () {
  return {
    restrict: 'A', // <-- and not "require"
    scope: {
      myFnViaAttribute: '='
    },
    controllerAs: 'chartCtrl',
    bindToController: true,
    controller: function ($scope) {
      $scope.myFnViaAttribute = function () {
        console.log("myFnViaAttribute called");
      }
    }
  };
})
floribon
  • 19,175
  • 5
  • 54
  • 66
  • Thanks @floribon, that works for AngularJS 1.1.0 but not for AngularJS (see http://plnkr.co/edit/Cs9wZwnFAvASJdfrZZTW?p=preview). – Michael Osofsky Apr 14 '15 at 22:52
  • 1
    Now I actually look at the code I am scared, this looks overcomplicated. What are you trying to do exactly? – floribon Apr 14 '15 at 23:01
  • Make a "component" and expose a public API on it. Same goal as http://stackoverflow.com/questions/15934500/how-to-expose-behavior-from-directive-with-isolated-scope. Only problem is the solution didn't work on the latest AngularJS (1.4.0-beta6 nor 1.3.15 which are most current according to https://developers.google.com/speed/libraries/). – Michael Osofsky Apr 14 '15 at 23:04
  • 1
    You first need to use the `&` binding (one-way parsing) instead of a two-way `=`. But you cannot expect that `ng-click` in your code to have access to `fnInCtrlViaAttribute` since they are sibling elements, they don't share the same scope. – floribon Apr 14 '15 at 23:06
  • 1
    If you want describe a simple "real world" case for what you try to do, just use words and not code, and we will show you how to expose that in Angular. You can totally expose a "public API" using expression bindings. – floribon Apr 14 '15 at 23:07
  • Thanks @floribon. The user should be able to enter a bunch of data and see a chart. The code for inputting and outputting should be reusable components for other applications. I originally described it here: http://stackoverflow.com/questions/29527463/how-propagate-form-submit-event-to-parent-of-custom-directive. Got the solution almost working but I'm stuck figuring out how the parent component (`NavigationController`) should tell the output component (`OutputController`) to render. – Michael Osofsky Apr 14 '15 at 23:15
  • 1
    Hmm, you should read more about controllers interactions: you can either expose a `&` binding indeed, or rely on events (`$broadcast`, `$on`, etc). Using a two ways binding `=` passing a function can work too (but smells bad). – floribon Apr 14 '15 at 23:21
  • I'm coming from Objective C where exposing public APIs of components is well documented. A pointer to the documentation that's most about how to implement MVC in AngularJS would be most appreciated. Do you recommend the & binding? Can it work or will my code not have access to `fnInCtrlViaAttribute` as you said earlier? – Michael Osofsky Apr 14 '15 at 23:25
  • 1
    Your code has many structural issues. You should forget about it and start from scratch. Besires that `ng-click` that has no access to the isolated directive scopes, another big problem is that you redefine `$scope.myFnViaAttribute = ...` in your directive when you have it as a binding: one will override the other. Have a look a this. be sure to understand it, and then think how this could apply to your code: http://plnkr.co/edit/frqOtw9eEVKPogpjFoQj?p=preview – floribon Apr 14 '15 at 23:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/75242/discussion-between-michael-osofsky-and-floribon). – Michael Osofsky Apr 14 '15 at 23:57