53

plunker: http://plnkr.co/edit/wURNg8ByPYbEuQSL4xwg

example.js:

angular.module('plunker', ['ui.bootstrap']);
  var ModalDemoCtrl = function ($scope, $modal) {

  $scope.open = function () {
    var modalInstance = $modal.open({
      templateUrl: 'modal.html',
      controller: 'ModalInstanceCtrl'
    });
  };
};

var ModalInstanceCtrl = function ($scope, $modalInstance) {

  $scope.ok = function () {
    alert($scope.text);
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
};

index.html:

<!doctype html>
<html ng-app="plunker">
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js"></script>
    <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
    <script src="example.js"></script>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body>

  <div ng-controller="ModalDemoCtrl">
    <button class="btn" ng-click="open()">Open me!</button>
    <div ng-show="selected">Selection from a modal: {{ selected }}</div>
  </div>
 </body>
</html>

modal.html:

<div class="modal-header">
    <h3>I'm a modal!</h3>
</div>
<textarea ng-model="text"></textarea>
<div class="modal-footer">
    <button class="btn btn-primary" ng-click="ok()">OK</button>
    <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>

Why I can't get the $scope.text defined in ModalInstanceCtrl, even though I can use $scope.ok and $scope.cancel?

Manuel Bitto
  • 5,073
  • 6
  • 39
  • 47

3 Answers3

72

Looks like a scope issue. I got it to work like this:

var ModalInstanceCtrl = function ($scope, $modalInstance) {
    $scope.input = {};
    $scope.ok = function () {
        alert($scope.input.abc);
    };

    $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
    };
};

HTML:

<textarea ng-model="input.abc"></textarea>
AlwaysALearner
  • 43,759
  • 9
  • 96
  • 78
  • 3
    Yes, initializing `$scope.input = {};` fixes it. Strange thing though. – Manuel Bitto Sep 10 '13 at 10:36
  • 5
    This is not strange, this is by design since the modal actually created a sub controller, and JS behavior is that if you try to get an object and your object doesn't have it than it will go to parent – Ran Davidovitz Oct 02 '13 at 05:03
  • 3
    Right, this is because of Javascript's prototypical inheritance. It can't find "input" on the current scope, so it checks that scope's prototype and so on until it happens upon an "input" property on one of the scopes in the prototype chain. This "input" property is an object so you have a reference to it and are able set an "abc" property on it. This little trick is often used in angular to manipulate a value somewhere in an ancestor scope. – David Sanders Nov 06 '13 at 21:17
  • 2
    True, nothing strange about it - although Javascript's prototypical inheritance might be difficult to grasp. I strongly recommend this read for clarification: https://github.com/angular/angular.js/wiki/Understanding-Scopes – Kulbi Dec 03 '13 at 11:37
  • I believe it's considered best practice in angular to bind all your scope variables as objects so that you don't have to worry about passing variables by value, and you maintain your 2 way binding – Matt Kim Mar 15 '14 at 06:45
  • Another thing you could do is pass whatever you need to "ok()" function. `ok(text)`. Actually, more powerful in the long run. – Ivan P Apr 03 '14 at 18:47
  • [Here](https://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m) and [here](https://github.com/angular/angular.js/wiki/Understanding-Scopes) are the best explanations of this in my opinion. – bobjones Apr 09 '14 at 19:26
16

Update Nov 2014: the issue is fixed with angular-ui-bootstrap 0.12.0 - the transclusion scope is merged with the controller's scope. There is no need to do anything. Just stay with:

<textarea ng-model="text"></textarea>

Before 0.12.0:

Angular-UI modals are using transclusion to attach modal content, which means any new scope entries made within modal are created in child scope.

You should use inheritance and initialize empty text entry in parent $scope or you can explicitly attach the input to parent scope:

<textarea ng-model="$parent.text"></textarea>
gertas
  • 16,869
  • 1
  • 76
  • 58
3

Let'me try to explain the reason. ui-bootstrap modal sourcecode:

.directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
return {
  restrict: 'EA',
  scope: {
    index: '@',
    animate: '='
  },
  replace: true,
  transclude: true,
  templateUrl: function(tElement, tAttrs) {
    return tAttrs.templateUrl || 'template/modal/window.html';
  },

and the template sourcecode - window.html:

<div tabindex="-1" role="dialog" class="modal fade" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}" ng-click="close($event)">
<div class="modal-dialog" ng-class="{'modal-sm': size == 'sm', 'modal-lg': size == 'lg'}"><div class="modal-content" modal-transclude></div></div>

there is a directive modal-transclude,your dialog content will insert into it, it's sourcecode:

.directive('modalTransclude', function () {
return {
  link: function($scope, $element, $attrs, controller, $transclude) {
    $transclude($scope.$parent, function(clone) {
      $element.empty();
      $element.append(clone);
    });
  }
};

})

now take a look at offical doc of $compile:

Transclusion Functions

When a directive requests transclusion, the compiler extracts its contents and provides 
a transclusion function to the directive's link function and controller. 
This transclusion function is a special linking function that will return the compiled 
contents linked to a **new transclusion scope.**

transclude will create a new scope of controller scope

笑笑十年
  • 101
  • 5