78

我一直在将我的自定义指令升级到新的组件架构。我读过组件不支持观察者。这个对吗?如果是这样,您如何检测对象的变化?对于一个基本示例,我有一个自定义组件myBox,它有一个带有游戏绑定的子组件游戏。如果游戏组件中有更改游戏,我如何在 myBox 中显示警报消息?我知道有 rxJS 方法是否可以纯粹以角度来做到这一点?我的JSFiddle

JavaScript

var app = angular.module('myApp', []);
app.controller('mainCtrl', function($scope) {

   $scope.name = "Tony Danza";

});

app.component("myBox",  {
      bindings: {},
      controller: function($element) {
        var myBox = this;
        myBox.game = 'World Of warcraft';
        //IF myBox.game changes, show alert message 'NAME CHANGE'
      },
      controllerAs: 'myBox',
      templateUrl: "/template",
      transclude: true
})
app.component("game",  {
      bindings: {game:'='},
      controller: function($element) {
        var game = this;


      },
      controllerAs: 'game',
      templateUrl: "/template2"
})

HTML

<div ng-app="myApp" ng-controller="mainCtrl">
  <script type="text/ng-template" id="/template">
    <div style='width:40%;border:2px solid black;background-color:yellow'>
      Your Favourite game is: {{myBox.game}}
      <game game='myBox.game'></game>
    </div>
  </script>

 <script type="text/ng-template" id="/template2">
    <div>
    </br>
        Change Game
      <textarea ng-model='game.game'></textarea>
    </div>
  </script>

  Hi {{name}}
  <my-box>

  </my-box>

</div><!--end app-->
4

7 回答 7

159

在没有观察者的情况下编写组件

这个答案概述了在不使用观察者的情况下编写 AngularJS 1.5 组件的五种技术。


使用ng-change指令

哪些 alt 方法可用于观察 obj 状态变化而无需使用 watch 来为 AngularJs2 做准备?

您可以使用该ng-change指令对输入更改做出反应。

<textarea ng-model='game.game' 
          ng-change="game.textChange(game.game)">
</textarea>

并且要将事件传播到父组件,需要添加事件处理程序作为子组件的属性。

<game game='myBox.game' game-change='myBox.gameChange($value)'></game>

JS

app.component("game",  {
      bindings: {game:'=',
                 gameChange: '&'},
      controller: function() {
        var game = this;
        game.textChange = function (value) {
            game.gameChange({$value: value});
        });

      },
      controllerAs: 'game',
      templateUrl: "/template2"
});

在父组件中:

myBox.gameChange = function(newValue) {
    console.log(newValue);
});

这是未来的首选方法。AngularJS 的使用策略$watch是不可扩展的,因为它是一种轮询策略。当$watch听众数量达到 2000 左右时,UI 变得迟缓。Angular 2中的策略是使框架更具反应性并避免将$watch.$scope


使用$onChanges生命周期钩子

1.5.3 版本中,AngularJS$onChanges为服务添加了生命周期钩子$compile

从文档:

控制器可以提供以下方法作为生命周期钩子:

  • $onChanges(changesObj) - 每当更新单向 ( <) 或插值 ( @) 绑定时调用。是一个散列,其changesObj键是已更改的绑定属性的名称,值是表单的对象{ currentValue: ..., previousValue: ... }。使用此钩子触发组件内的更新,例如克隆绑定值以防止外部值的意外突变。

— AngularJS 综合指令 API 参考 — 生命周期钩子

$onChanges钩子用于通过<单向绑定对组件的外部更改做出反应。该ng-change指令用于通过绑定从ng-model组件外部的控制器传播更改。&


使用$doCheck生命周期钩子

1.5.8 版本中,AngularJS$doCheck为服务添加了生命周期钩子$compile

从文档:

控制器可以提供以下方法作为生命周期钩子:

  • $doCheck()- 在摘要循环的每一轮调用。提供检测变化并采取行动的机会。必须从这个钩子中调用您为响应检测到的更改而希望采取的任何操作;实现这一点对何时$onChanges调用没有影响。例如,如果您希望执行深度相等检查或检查 Date 对象,Angular 的更改检测器不会检测到更改,因此不会触发,则此挂钩可能很有用$onChanges。这个钩子是不带参数调用的;如果检测到更改,则必须存储以前的值以与当前值进行比较。

— AngularJS 综合指令 API 参考 — 生命周期钩子


组件间通信require

指令可以要求其他指令的控制器启用彼此之间的通信。这可以通过为require属性提供对象映射在组件中实现。对象键指定所需的控制器(对象值)将绑定到所需组件的控制器的属性名称。

app.component('myPane', {
  transclude: true,
  require: {
    tabsCtrl: '^myTabs'
  },
  bindings: {
    title: '@'
  },
  controller: function() {
    this.$onInit = function() {
      this.tabsCtrl.addPane(this);
      console.log(this);
    };
  },
  templateUrl: 'my-pane.html'
});

有关更多信息,请参阅AngularJS 开发人员指南 - 组件间通信


使用RxJS从服务推送值

例如,在您拥有一个保持状态的服务的情况下呢?我怎样才能将更改推送到该服务,页面上的其他随机组件才能知道这种更改?最近一直在努力解决这个问题

使用RxJS Extensions for Angular构建服务。

<script src="//unpkg.com/angular/angular.js"></script>
<script src="//unpkg.com/rx/dist/rx.all.js"></script>
<script src="//unpkg.com/rx-angular/dist/rx.angular.js"></script>
var app = angular.module('myApp', ['rx']);

app.factory("DataService", function(rx) {
  var subject = new rx.Subject(); 
  var data = "Initial";

  return {
      set: function set(d){
        data = d;
        subject.onNext(d);
      },
      get: function get() {
        return data;
      },
      subscribe: function (o) {
         return subject.subscribe(o);
      }
  };
});

然后只需订阅更改。

app.controller('displayCtrl', function(DataService) {
  var $ctrl = this;

  $ctrl.data = DataService.get();
  var subscription = DataService.subscribe(function onNext(d) {
      $ctrl.data = d;
  });

  this.$onDestroy = function() {
      subscription.dispose();
  };
});

客户端可以使用 订阅更改,DataService.subscribe生产者可以使用 推送更改DataService.set

PLNKR 上的演示。

于 2016-02-21T11:10:58.370 回答
8

$watch对象在对象内部可用$scope,因此您需要$scope在控制器工厂函数中添加然后将观察者放在变量上。

$scope.$watch(function(){
    return myBox.game;
}, function(newVal){
   alert('Value changed to '+ newVal)
});

在这里演示

注意:我知道您已经转换directivecomponent, 以删除对 的依赖,$scope这样您就离 Angular2 更近了一步。但它似乎并没有因为这种情况而被删除。

更新

基本上 angular 1.5 确实添加了.component方法来区分两种不同的功能。Like .代表通过添加component来执行特定的行为selector,而 asdirective代表向 DOM 添加特定的行为。指令只是.directiveDDO(指令定义对象)的包装方法。只有你能看到的是,他们在使用你有能力获得角度编译的 DOM 的方法link/compile时具有删除功能。.component

使用Angular 组件生命周期钩子的$onChanges/$doCheck生命周期钩子,这些将在 Angular 1.5.3+ 版本之后可用。

$onChanges(changesObj) - 每当绑定更新时调用。changesObj 是一个散列,其键是绑定属性的名称。

$doCheck() - 在绑定更改时在摘要循环的每一轮调用。提供检测变化并采取行动的机会。

通过在组件内使用相同的功能将确保您的代码兼容以迁移到 Angular 2。

于 2016-02-21T10:11:30.787 回答
4

对于任何对我的解决方案感兴趣的人,我最终会求助于 RXJS Observables,当您使用 Angular 2 时,您将不得不使用它。这是组件之间通信的工作小提琴,它让我可以更好地控制观看的内容。

JS FIDDLE RXJS Observables

class BoxCtrl {
    constructor(msgService) {
    this.msgService = msgService
    this.msg = ''

    this.subscription = msgService.subscribe((obj) => {
      console.log('Subscribed')
      this.msg = obj
    })
    }

  unsubscribe() {
    console.log('Unsubscribed')
    msgService.usubscribe(this.subscription)
  }
}

var app = angular
  .module('app', ['ngMaterial'])
  .controller('MainCtrl', ($scope, msgService) => {
    $scope.name = "Observer App Example";
    $scope.msg = 'Message';
    $scope.broadcast = function() {
      msgService.broadcast($scope.msg);
    }
  })
  .component("box", {
    bindings: {},
    controller: 'BoxCtrl',
    template: `Listener: </br>
    <strong>{{$ctrl.msg}}</strong></br>
    <md-button ng-click='$ctrl.unsubscribe()' class='md-warn'>Unsubscribe A</md-button>`
  })
  .factory('msgService', ['$http', function($http) {
    var subject$ = new Rx.ReplaySubject();
    return {
      subscribe: function(subscription) {
        return subject$.subscribe(subscription);
      },
      usubscribe: function(subscription) {
        subscription.dispose();
      },
      broadcast: function(msg) {
        console.log('success');
        subject$.onNext(msg);
      }
    }
  }])
于 2016-07-06T06:46:38.477 回答
2

一个关于使用的小提示ng-change,如已接受的答案所推荐的那样,以及一个角度 1.5 组件。

如果您需要监视一个不工作的组件,您可以将参数传递为ng-modelng-change

使用组件的标记:

<my-component on-change="$ctrl.doSth()"
              field-value="$ctrl.valueToWatch">
</my-component>

组件js:

angular
  .module('myComponent')
  .component('myComponent', {
    bindings: {
      onChange: '&',
      fieldValue: '='
    }
  });

组件标记:

<select ng-model="$ctrl.fieldValue"
        ng-change="$ctrl.onChange()">
</select>
于 2016-10-03T15:23:57.230 回答
0

在 IE11 中可用,MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver。您需要将 $element 服务注入到控制器中,这会半破坏 DOM/控制器分离,但我觉得这是 angularjs 中的一个基本异常(即缺陷)。由于隐藏/显示是异步的,我们需要显示回调,而 angularjs 和 angular-bootstrap-tab 不提供。它还要求你知道你想观察哪个特定的 DOM 元素。我使用以下代码用于 angularjs 控制器来触发 Highcharts 图表重排。

const myObserver = new MutationObserver(function (mutations) {
    const isVisible = $element.is(':visible') // Requires jquery
    if (!_.isEqual(isVisible, $element._prevIsVisible)) { // Lodash
        if (isVisible) {
            $scope.$broadcast('onReflowChart')
        }
        $element._prevIsVisible = isVisible
    }
})
myObserver.observe($element[0], {
    attributes: true,
    attributeFilter: ['class']
})
于 2017-10-30T18:10:01.983 回答
0

我迟到了。但它可以帮助其他人。

app.component("headerComponent", {
    templateUrl: "templates/header/view.html",
    controller: ["$rootScope", function ($rootScope) {
        let $ctrl = this;
        $rootScope.$watch(() => {
            return $ctrl.val;
        }, function (newVal, oldVal) {
            // do something
        });
    }]
});
于 2018-11-05T16:50:50.113 回答
0

真的很好接受的答案,但我可能会补充一点,你也可以使用事件的力量(如果你愿意的话,有点像 Qt 信号/插槽)。

一个事件被广播:$rootScope.$broadcast("clickRow", rowId) 由任何父母(甚至孩子控制器)。然后在您的控制器中,您可以像这样处理事件:

$scope.$on("clickRow", function(event, data){
    // do a refresh of the view with data == rowId
});

您还可以像这样添加一些日志记录(取自这里:https ://stackoverflow.com/a/34903433/3147071 )

var withLogEvent = true; // set to false to avoid events logs
app.config(function($provide) {
    if (withLogEvent)
    {
      $provide.decorator("$rootScope", function($delegate) {
        var Scope = $delegate.constructor;
        var origBroadcast = Scope.prototype.$broadcast;
        var origEmit = Scope.prototype.$emit;

        Scope.prototype.$broadcast = function() {
          console.log("$broadcast was called on $scope " + this.$id + " with arguments:",
                     arguments);
          return origBroadcast.apply(this, arguments);
        };
        Scope.prototype.$emit = function() {
          console.log("$emit was called on $scope " + this.$id + " with arguments:",
                     arguments);
          return origEmit.apply(this, arguments);
        };
        return $delegate;
      });
    }
});
于 2018-04-03T08:58:26.997 回答