20

I've created a simple "modal dialog" directive, which uses transclude. I would like to place a form () inside the "modal dialog" directive. I would expect that formController of a form placed inside the directive, is going to be accessible in parent controller's scope, however it isn't. Take a look at the following fiddle, please: http://jsfiddle.net/milmly/f2WMT/1/

Complete code:

<!DOCTYPE html>
<html>
    <head>
        <title>AngJS test</title>
        <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/foundation/4.0.9/css/foundation.min.css">
        <style>
            .reveal-modal {
                display: block;
                visibility: visible;
            }
        </style>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular.min.js"></script>
        <script type="text/javascript">
            var app = angular.module('app', []);
            app.controller('appCtrl', function ($scope) {
                $scope.model = {
                    id: 1, name: 'John'
                };
                $scope.modal = {
                    show: false
                };
           });
           app.directive('modal', function () {
               return {
                   scope: {
                       show: '='
                   },
                   transclude: true,
                   replace: true,
                   template: '<div class="reveal-modal small" ng-show="show"><div class="panel" ng-transclude></div></div>'
               }
           });
       </script>
    </head>
    <body ng-app="app">
        <div ng-controller="appCtrl">
            <div class="panel">
                Id: {{ model.id }}<br>
                Name: {{ model.name }}<br>
                Controller formController: {{ form }}<br>
                Directive formController: {{ myForm }}<br>
            </div>

            <form name="form" class="panel">
                <input type="text" ng-model="model.name">
            </form>

            <a ng-click="modal.show=!modal.show">toggle dialog</a>

            <div modal show="modal.show">
                <form name="myForm">
                    <input type="text" ng-model="model.name">
                </form>
            </div>

        </div>
    </body>
</html>

So my question is how to access or is it possible to access directive's formController from parent controller?

Thank you for answers.

-Milan

MilMly
  • 213
  • 1
  • 2
  • 6
  • Is that the right fiddle? No reference to `$scope.myForm` anywhere. – Langdon Apr 10 '13 at 20:01
  • There's a {{ myForm }} in HTML, which is same as $scope.myForm, I guess. – MilMly Apr 10 '13 at 20:22
  • There's also {{ form }}, which references a form inside controller but outside of directive. This one works as expected, but myForm does not get to parent scope - appCtrl's scope :-( – MilMly Apr 10 '13 at 20:32
  • I really think you linked the wrong fiddle. There's no `myForm`, there's no `appCtrl`, and there's no `formController` in that fiddle. – Langdon Apr 10 '13 at 20:45
  • Yes, you're right, I forgot to update fiddle, thank you for notice. I updated the post with complete source as well as correct fiddle link. Thank you again :-) – MilMly Apr 10 '13 at 20:51

2 Answers2

28

Because you are using transclude, the directive will create a child transcluded scope. There is no easy path from the controller scope (003) to the directive's transcluded scope (005):

enter image description here

(The hard/not recommended path is to go via private property $$childHead on the controller scope, find the isolate scope, then use $$nextSibling to get to the transcluded scope.)


Update: From insights from this answer, I think we can get the formController inside the directive, then use = to get it to the parent.

scope: { show: '=', formCtrl: '=' },
...
link: function(scope, element) {
   var input1 = element.find('input').eq(0);
   scope.formCtrl = input1.controller('form');
}

HTML:

<div modal show="modal.show" form-ctrl="formCtrl">

Fiddle

enter image description here

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 5
    What generated those beautiful graphics? – Langdon Apr 11 '13 at 03:42
  • Perfect! :) Thank you Mark. I forked your fiddle to make it more general, so it looks for the form element instead of the first input: http://jsfiddle.net/milmly/utnd6/ – MilMly Apr 11 '13 at 06:02
  • 7
    @Langdon, I have a tool that I wrote/am writing. I use GraphViz's dot to generate the pictures. I have a directive that figures out the scope properties, and some Python code to generate the dot files. – Mark Rajcok Apr 11 '13 at 14:31
  • @MarkRajcok I'd love to see this tool when your finished, or at least a sample `.dot` file where I could copy the styles from! :) – a paid nerd May 24 '13 at 00:43
  • 3
    @apaidnerd, here's a [sample .dot file](https://github.com/mrajcok/angularjs-prototypal-inheritance-diagrams/blob/master/sample_from_new_tool.dot). If you want to add a label, see http://stackoverflow.com/a/15707752/215945 – Mark Rajcok May 25 '13 at 16:18
  • 2
    @apaidnerd, the tool is finished and on github: [Peri$scope](https://github.com/mrajcok/angularjs-periscope). – Mark Rajcok Jan 20 '16 at 15:00
10

Here is my solution: I create such method in parent controller :

$scope.saveForm = function(form) {
  $scope.myForm = form;
};

Then I call it in transcluded content:

<my-directive>
  <form name="myForm">
     <div ng-init="saveForm(myForm)"></div>
  </form>
</my-directive>

After creating directive instance i have form controller instance in parent scope.

Eugene Gluhotorenko
  • 3,094
  • 2
  • 33
  • 52
  • No really actually. Angular supposes controllers to deal with only data model. But anyway it is the best hack i found. – Eugene Gluhotorenko Apr 10 '15 at 09:54
  • This method works great when you cannot edit the directive, such as when using Angular UI Bootstrap directives. See my issue here which was resolved with your method: http://stackoverflow.com/questions/37727443/angular-js-forms-set-pristine – Steve Jun 09 '16 at 14:22