在AngularJS中,在控制器和指令之间共享作用域

8
我创建了一个指令来包装一个jQuery插件,并从控制器向指令传递一个配置对象以供插件使用。(正常工作)
在配置对象中有一个回调函数,我想在事件发生时调用它。(正常工作)
在回调函数中,我想修改控制器$scope上的属性,但这并不起作用。由于某种原因,Angular不能识别属性已更改,这使我相信回调函数中的$scope与控制器的$scope不同。我的问题是我不知道为什么。
有人能指点我正确的方向吗?

点击此处查看 Fiddle


app.js

var app = angular.module('app', [])
    .directive('datepicker', function () {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                // Uncommenting the line below causes
                // the "date changed!" text to appear,
                // as I expect it would.
                // scope.dateChanged = true;

                var dateInput = angular.element('.datepicker')

                dateInput.datepicker(scope.datepickerOpts);

                // The datepicker fires a changeDate event
                // when a date is chosen. I want to execute the
                // callback defined in a controller.
                // ---
                // PROBLEM: 
                // Angular does not recognize that $scope.dateChanged
                // is changed in the callback. The view does not update.
                dateInput.bind('changeDate', scope.onDateChange);
            }
        };
    });

var myModule = angular.module('myModule', ['app'])
    .controller('MyCtrl', ['$scope', function ($scope) {
        $scope.dateChanged = false;

        $scope.datepickerOpts = {
            autoclose: true,
            format: 'mm-dd-yyyy'
        };

        $scope.onDateChange = function () {
            alert('onDateChange called!');

            // ------------------
            // PROBLEM AREA:
            // This doesnt cause the "date changed!" text to show.
            // ------------------
            $scope.dateChanged = true;

            setTimeout(function () {
                $scope.dateChanged = false;
            }, 5000);
        };
    }]);

HTML

<div ng-controller="MyCtrl">
    <p ng-show="dateChanged">date changed!</p>
    <input type="text" value="02-16-2012" class="datepicker" datepicker="">
</div>
2个回答

11

你的演示中存在一些作用域问题。首先,在dateChange回调函数中,即使函数本身在控制器内声明,由于它位于bootstrap处理程序中,回调函数中this的上下文是bootstrap元素。

每当你从第三方代码中更改angular作用域值时,需要使用$apply通知angular。通常最好将所有第三方作用域保留在指令内部。

更加angular的方法是在输入框上使用ng-model,然后使用$watch来监听模型的变化。这有助于将所有代码保留在控制器内部的angular上下文中。在任何angular应用程序中,很少会不使用ng-model在任何表单控件上。

 <input type="text"  class="datepicker" datepicker="" ng-model="myDate">

指令内:

dateInput.bind('changeDate',function(){
      scope.$apply(function(){
         scope[attrs.ngModel] = element.val()
      });
});

然后在控制器中:

$scope.$watch('myDate',function(oldVal,newVal){ 
       if(oldVal !=newVal){
           /* since this code is in angular context will work for the hide/show now*/
            $scope.dateChanged=true;
             $timeout(function(){
                   $scope.dateChanged=false;
              },5000);
        }        
});

演示:http://jsfiddle.net/qxjck/10/

编辑:还有一项需要更改的是,如果您希望在页面上使用此指令,那么应该删除var dateInput = angular.element('.datepicker')。它在指令中被使用是多余的,在link回调函数中的element已经是实例特定的参数之一。请将dateInput替换为element.


还有一个想法...在Github上有几个非常容易使用的Angular/Bootstrap UI指令项目。 - charlietfl
谢谢你纠正我。我用$apply立刻就搞定了(真想拥抱你),但我想要正确的做法,所以我正在尝试实现你展示的方法。在我的演示中,我没有展示出来,但我实际上是使用一个<a>标签来触发日期选择器的。所选日期保存在element.data('datepicker').getDate()中。我对AngularJS还很陌生,如果我说错了,请原谅我,但ng-model是用于表单元素的。有什么适当的替代方案吗? - simshaun
我对 Bootstrap datepicker 没有太多的熟悉,但是我比较了解 jQuery Ui。然而,可以在输入框上使用 ng-model,也可以硬编码更新控制器范围内的变量,或者使用任何自定义属性来存储变量名称(例如,在页面的多个位置使用此属性)。如果有更多问题,可以创建一个包含 <a> 标签实现的更新版本。在指令中,在 scope.$apply 调用中以数据元素来源轻松修改。保持相同的 $watch 方法论。 - charlietfl
酷......一开始弄清楚Angular的微妙差别有点令人沮丧,但从长远来看非常值得。 - charlietfl
应该使用 $parse 服务来代替 scope[attrs.ngModel],因为 $parse 可以处理 obj.myDate 这种 ng-model 值,参见 fiddle。在这种情况下, scope[attrs.ngModel] 不起作用。 - Mark Rajcok

2

绑定在输入框上的changeDate事件似乎是在Angular框架之外设置的。要显示该段落,请在将dateChanged设置为true后调用$scope.$apply()。要在延迟后隐藏该段落,您可以再次在传递给setTimeout的函数中使用$apply(),但最好使用Angular的$timeout()以避免进一步的问题。

Fiddle


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接