Search code examples
angularjsangular-directive

Controller $scope and <form> in TranscludeScope


I have a directive that has a transclude. The transcluded content is a form which has a submit option calling a method in the (parent) controller. This method is called from the transcluded scope so any variables I access in the controller I can access via this.variable as this reflects the callers (current) scope. However, $scope.variable is undefined as $scope is that of the parent scope, which is the scope of the controller.

Should I indeed use this to access the forms values or is there a convention that I should implement to access via $scope in the (parent) controller?

Disclaimer: I know I'm not using controllerAs functionality, and right now I am not able to change that for the project.

Plunker to demonstrate the issue: http://plnkr.co/edit/TdgZFIRNbUcHNfe3a7uO?p=preview

As you can see the value inside the directive updates when you change the value in the textbox. This is as expected as both refer to the transcluded scope. The {{name}} outside the directive does not change as this is the parent scope and not the transcluded scope. One-way traffic.

What is the proper solution? Use this.name or modify the directive so that $scope.name can be used outside the directive?

For those who like code over plunkers:

HTML:

<body ng-app="docsTransclusionExample">
  <div ng-controller="Controller">
    {{name}}
    <my-dialog>
      Check out the contents, {{name}}!
      <form>
        <input ng-model="name">
      </form>
    </my-dialog>
  </div>
</body>

Directive:

(function(angular) {
  'use strict';
angular.module('docsTransclusionExample', [])
  .controller('Controller', function($scope) {
    $scope.name = 'Tobias';
  })
  .directive('myDialog', function() {
    return {
      restrict: 'E',
      transclude: true,
      scope: {},
      templateUrl: 'my-dialog.html'
    };
  });
})(window.angular);

Directive template:

<div class="alert" ng-transclude></div>

Solution

  • This is related to "The Dot Rule" in angular.

    This means, that when you have scope heirarchy, and a child scope is trying to modify a parent scope, it's creating it's own scope.

    When you use data objects, you access members "with a dot", and so you don;t change the scope directly, and not creating a new child scope

    In your case

    $scope.name = 'Tobias';
    

    shuold change to

    $scope.data = {name: 'Tobias'};
    

    see plunker: http://plnkr.co/edit/RqORVm8r4jph532dT6nY?p=preview

    for further reading:

    Why don't the AngularJS docs use a dot in the model directive?

    https://www.youtube.com/watch?v=DTx23w4z6Kc

    PS.

    1. This is not related to directive transclusion (whitch is about building the DOM).
    2. If you do: scope: {} in directive, you explicitally create a child scope.