【问题标题】:Angularjs: 'controller as syntax' and $watchAngularjs:“控制器作为语法”和 $watch
【发布时间】:2014-07-27 12:42:16
【问题描述】:

使用controller as语法时如何订阅属性更改?

controller('TestCtrl', function ($scope) {
  this.name = 'Max';
  this.changeName = function () {
    this.name = new Date();
  }
  // not working       
  $scope.$watch("name",function(value){
    console.log(value)
  });
});
<div ng-controller="TestCtrl as test">
  <input type="text" ng-model="test.name" />
  <a ng-click="test.changeName()" href="#">Change Name</a>
</div>  

【问题讨论】:

  • this.$watch() 怎么样?它是有效的:this.$watch('name', ...)

标签: javascript angularjs angularjs-scope ng-controller


【解决方案1】:

只需绑定相关上下文即可。

$scope.$watch(angular.bind(this, function () {
  return this.name;
}), function (newVal) {
  console.log('Name changed to ' + newVal);
});

示例:http://jsbin.com/yinadoce/1/edit

更新:

Bogdan Gersak 的答案实际上是等价的,两个答案都尝试将 this 与正确的上下文绑定。但是,我发现他的答案更清晰。

话虽如此,首先,你必须了解the underlying idea behind it

更新 2:

对于那些使用 ES6 的人,通过使用arrow function,您可以获得一个具有正确上下文 OOTB 的函数。

$scope.$watch(() => this.name, function (newVal) {
  console.log('Name changed to ' + newVal);
});

Example

【讨论】:

  • 我们可以不使用 $scope 来避免 this 和 $scope 混用吗?
  • 据我所知没有,但完全没问题。 $scope for you 是一种提供这些方法的服务。
  • 你能澄清一下return this.name;中的name是指控制器的名称还是这里的属性“name”?
  • @Jannik, angular.bind 返回一个有界上下文(arg #1)的函数。在我们的例子中,我们将控制器实例this绑定到函数(arg #2),所以this.name表示控制器实例的属性name
  • 我想我刚刚明白这是如何工作的。当绑定函数被调用时,它只是简单地计算为被监视的值,对吧?
【解决方案2】:

我通常这样做:

controller('TestCtrl', function ($scope) {
    var self = this;

    this.name = 'Max';
    this.changeName = function () {
        this.name = new Date();
   }

   $scope.$watch(function () {
       return self.name;
   },function(value){
        console.log(value)
   });
});

【讨论】:

  • 我同意这是最好的答案,尽管我要补充一点,对此的混淆可能是在传递一个函数作为$scope.$watch 中的第一个参数并使用该函数从关闭。我还没有遇到过另一个例子,但它有效并且是最好的。我没有选择下面的答案(即$scope.$watch('test.name', function (value) {});)的原因是因为它要求我在模板或 ui.router 的 $stateProvider 中对我命名的控制器进行硬编码,并且那里的任何更改都会无意中破坏观察者.
  • 此外,此答案与目前接受的答案(使用 angular.bind)之间的唯一实质性区别是您是要绑定到 this 还是只是在关闭。这些在功能上是等效的,并且根据我的经验,这种选择通常是一种主观要求和非常强烈的意见。
  • ES6 的一个好处是消除了必须执行上述两种变通方法来获得正确的 js 范围。 $scope.$watch( ()=&gt; { return this.name' }, function(){} )救命的肥箭
  • 你也可以() =&gt; this.name
  • 您能否使用$scope.$watchCollection 完成这项工作,并且仍然获得oldVal, newVal 参数?
【解决方案3】:

你可以使用:

   $scope.$watch("test.name",function(value){
        console.log(value)
   });

这适用于您的示例JSFiddle

【讨论】:

  • 这种方法的问题是 JS 现在依赖于 HTML,迫使控制器在任何地方都绑定为相同的名称(在本例中为“test”),以便 $watch工作。很容易引入细微的错误。
  • 如果你编写 Angular 1 和 Angular 2 一样,一切都是指令,那么这会非常有效。 Object.observe 现在会很棒。
【解决方案4】:

类似于使用“TestCtrl 作为测试”中的“测试”,如另一个答案中所述,您可以将“自我”分配给您的范围:

controller('TestCtrl', function($scope){
    var self = this;
    $scope.self = self;

    self.name = 'max';
    self.changeName = function(){
            self.name = new Date();
        }

    $scope.$watch("self.name",function(value){
            console.log(value)
        });
})

通过这种方式,您不会被绑定到 DOM 中指定的名称(“TestCtrl as test”),并且还避免了将 .bind(this) 绑定到函数的需要。

...用于指定的原始 html:

<div ng-controller="TestCtrl as test">
    <input type="text" ng-model="test.name" />
    <a ng-click="test.changeName()" href="#">Change Name</a>
</div>

【讨论】:

  • 只想知道一件事,即$scope是一个服务,所以如果我们添加$scope.self = this,那么在另一个控制器中如果我们这样做,会发生什么?
【解决方案5】:

AngularJs 1.5 支持 ControllerAs 结构的默认 $ctrl。

$scope.$watch("$ctrl.name", (value) => {
    console.log(value)
});

【讨论】:

  • 在使用 $watchGroup 时对我不起作用,这是已知限制吗?你能分享一个这个功能的链接吗,因为我找不到任何关于它的东西。
  • @user1852503 查看docs.angularjs.org/guide/component比较表指令/组件定义并检查'controllerAs'记录。
  • 我现在明白了。你的回答有点误导。标识符 $ctrl 与控制器作为一个特性没有关联(就像 $index 例如在 ng-repeat 中所做的那样),它恰好是组件内控制器的默认名称(问题甚至不是关于组件)。
  • @user1852503 1) $ctrl 关联控制器 (Controller as) 2) 问题是关于组件的,因为它提到:“
    ”。 3)此页面上的所有答案都与我的答案相同。 4) 关于文档 $watchGroup 在使用 $ctrl.name 时应该可以正常工作,因为它基于 $watch。
【解决方案6】:

你实际上可以传入一个函数作为 $watch() 的第一个参数:

 app.controller('TestCtrl', function ($scope) {
 this.name = 'Max';

// hmmm, a function
 $scope.$watch(function () {}, function (value){ console.log(value) });
 });

这意味着我们可以返回我们的 this.name 引用:

app.controller('TestCtrl', function ($scope) {
    this.name = 'Max';

    // boom
    $scope.$watch(angular.bind(this, function () {
    return this.name; // `this` IS the `this` above!!
    }), function (value) {
      console.log(value);
    });
});

阅读一篇关于控制器的有趣帖子https://toddmotto.com/digging-into-angulars-controller-as-syntax/

【讨论】:

    【解决方案7】:

    您可以使用 $onChanges 角度组件生命周期。

    在此处查看文档: https://docs.angularjs.org/guide/component 基于组件的应用程序部分

    【讨论】:

      【解决方案8】:

      用 ES6 语法编写 $watch 并不像我预期的那么容易。您可以执行以下操作:

      // Assuming
      // controllerAs: "ctrl"
      // or
      // ng-controller="MyCtrl as ctrl"
      export class MyCtrl {
        constructor ($scope) {
          'ngInject';
          this.foo = 10;
          // Option 1
          $scope.$watch('ctrl.foo', this.watchChanges());
          // Option 2
          $scope.$watch(() => this.foo, this.watchChanges());
        }
      
        watchChanges() {
          return (newValue, oldValue) => {
            console.log('new', newValue);
          }
        }
      }
      

      【讨论】:

        【解决方案9】:

        注意:当 View 和 Controller 在路由中或通过指令定义对象耦合时,这不起作用。下面显示的内容仅在 HTML 中有“SomeController as SomeCtrl”时有效。就像 Mark V. 在下面的评论中指出的那样,正如他所说的那样,最好像 Bogdan 那样做。

        我在控制器的开头使用:var vm = this; 来消除“this”这个词。然后vm.name = 'Max'; 和手表我return vm.name。我使用“vm”,就像@Bogdan 使用“self”一样。需要这个 var,无论是“vm”还是“self”,因为“this”这个词在函数内部具有不同的上下文。 (所以返回 this.name 是行不通的)是的,你需要在你漂亮的“controller as”解决方案中注入 $scope 以达到 $watch。请参阅 John Papa 的风格指南:https://github.com/johnpapa/angularjs-styleguide#controllers

        function SomeController($scope, $log) {
            var vm = this;
            vm.name = 'Max';
        
            $scope.$watch('vm.name', function(current, original) {
                $log.info('vm.name was %s', original);
                $log.info('vm.name is now %s', current);
            });
        }
        

        【讨论】:

        • 只要您的 HTML 中有“SomeController as vm”,它就可以工作。不过,这是一种误导:watch 表达式中的“vm.name”与“var vm = this;”无关。将 $watch 与“controller as”一起使用的唯一安全方法是将函数作为第一个参数传递,如 Bogdan 上面所示。
        【解决方案10】:

        这是在没有 $scope(和 $watch!)Top 5 Mistakes - Abusing watch 的情况下执行此操作的方法@

        如果您使用“controller as”语法,最好避免使用 $scope。

        这是我在JSFiddle 中的代码。 (我正在使用服务来保存名称,否则 ES5 Object.defineProperty 的 set 和 get 方法会导致无限调用。

        var app = angular.module('my-module', []);
        
        app.factory('testService', function() {
            var name = 'Max';
        
            var getName = function() {
                return name;
            }
        
            var setName = function(val) {
                name = val;
            }
        
            return {getName:getName, setName:setName};
        });
        
        app.controller('TestCtrl', function (testService) {
            var vm = this;
        
            vm.changeName = function () {
                vm.name = new Date();
            }
        
            Object.defineProperty(this, "name", {
                enumerable: true,
                configurable: false,
                get: function() {
                    return testService.getName();
                },
                set: function (val) {
                    testService.setName(val);
                    console.log(vm.name);
                }
            }); 
        });
        

        【讨论】:

        • 小提琴不工作,这不会观察对象属性。
        • @RooticalV。小提琴正在工作。 (确保在运行 AngualrJS 时,将负载类型指定为 nowrap-head/nowrap-body
        • 对不起,我还是没能成功运行它,太遗憾了,因为你的解决方案非常有趣
        • @happy 确保您选择的库为 Angular 1.4。 (我不确定 2.0 是否可以工作)并且加载类型为不换行,然后按运行。它应该可以工作。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-02-12
        • 1970-01-01
        • 2014-05-05
        • 2015-04-05
        • 2015-12-20
        • 2015-07-26
        • 2015-09-01
        相关资源
        最近更新 更多