AngularJS 1.5+ 组件不支持观察者,有什么解决办法?
AngularJS 1.5+ Components do not support Watchers, what is the work around?
我一直在将我的自定义指令升级到新的 component architecture. I've read that components do not support watchers. Is this correct? If so how do you detect changes on an object? For a basic example I have custom component myBox
which has a child component game with a binding on the game . If there is a change game within the game component how do I show an alert message within the myBox? I understand there is rxJS method is it possible to do this purely in angular? My 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-->
$watch
对象在 $scope
对象中可用,因此您需要在控制器工厂函数中添加 $scope
然后将观察者放在变量上。
$scope.$watch(function(){
return myBox.game;
}, function(newVal){
alert('Value changed to '+ newVal)
});
Note: I know you had converted directive
to component
, to remove dependency of $scope
so that you will get one step closer to
Angular2. But it seems like it didn't get removed for this case.
更新
基本上 angular 1.5 确实添加了 .component
方法来区分两种不同的功能。像 component
. 表示通过添加 selector
来执行特定行为,而 directive
表示向 DOM 添加特定行为。指令只是 .directive
DDO(指令定义对象)上的包装方法。只有你能看到的是,他们在使用 .component
方法时删除了 link/compile
函数,你可以在其中获得 angular 编译 DOM.
请使用 Angular 组件生命周期钩子的 $onChanges
/$doCheck
生命周期钩子,这些将在 Angular 1.5.3+ 版本后可用。
$onChanges(changesObj) - Called whenever bindings are updated. The changesObj is a hash whose keys are the names of the bound properties.
$doCheck() - Called on each turn of the digest cycle when binding changes. Provides an opportunity to detect and act on changes.
通过在组件内部使用相同的功能将确保您的代码兼容移动到 Angular 2.
在没有 Watchers
的情况下编写组件
此答案概述了五种技术,用于在不使用 watchers.
的情况下编写 AngularJS 1.5 组件
- 使用
ng-change
Directive
- 使用
$onChanges
Life-cycle Hook
- 使用
$doCheck
Life-cycle Hook
- 与 require
的组件间通信
- 使用 RxJS
从服务中推送值
使用ng-change
指令
what alt methods available to observe obj state changes without using watch in preparation for 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);
});
这是今后的首选方法。使用$watch
的AngularJS策略是不可扩展的,因为它是一个轮询策略。当 $watch
听众数量达到 2000 左右时,UI 变得迟钝。 Angular 2 中的策略是使框架更具反应性,避免将 $watch
放在 $scope
.
上
使用$onChanges
生命周期挂钩
使用 1.5.3 版本,AngularJS 将 $onChanges
生命周期挂钩添加到 $compile
服务。
来自文档:
The controller can provide the following methods that act as life-cycle hooks:
- $onChanges(changesObj) - Called whenever one-way (
<
) or interpolation (@
) bindings are updated. The changesObj
is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form { currentValue: ..., previousValue: ... }
. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value.
— AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
$onChanges
挂钩用于通过 <
单向绑定对组件的外部更改作出反应。 ng-change
指令用于从具有 &
绑定的组件外部的 ng-model
控制器传播更改。
使用 $doCheck
生命周期挂钩
使用 1.5.8 版本,AngularJS 将 $doCheck
生命周期挂钩添加到 $compile
服务。
来自文档:
The controller can provide the following methods that act as life-cycle hooks:
$doCheck()
- Called on each turn of the digest cycle. Provides an opportunity to detect and act on changes. Any actions that you wish to take in response to the changes that you detect must be invoked from this hook; implementing this has no effect on when $onChanges
is called. For example, this hook could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not be detected by Angular's change detector and thus not trigger $onChanges
. This hook is invoked with no arguments; if detecting changes, you must store the previous value(s) for comparison to the current values.
— AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
与 require
的组件间通信
指令可以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 Developer Guide - Intercomponent Communicatation
使用 RxJS
从服务中推送值
What about in a situation where you have a Service that's holding state for example. How could I push changes to that Service, and other random components on the page be aware of such a change? Been struggling with tackling this problem lately
使用 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
推送更改。
对于任何对我的解决方案感兴趣的人,我最终求助于 RXJS Observables,当你到达 Angular 2 时你将不得不使用它。这是一个有效的 fiddle 用于之间的通信组件,它让我可以更好地控制观看内容。
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);
}
}
}])
关于使用 ng-change
的小提示,建议使用已接受的答案,以及 angular 1.5 组件。
如果您需要观看 ng-model
和 ng-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>
在 IE11 中可用,MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver。您需要将 $element 服务注入到半中断 DOM/controller 分离的控制器中,但我认为这是 angularjs 中的一个基本异常(即缺陷)。由于 hide/show 是异步的,我们需要 on-show 回调,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']
})
接受的答案非常好,但我可能会补充一点,您也可以使用事件的力量(如果您愿意的话,有点像 Qt 信号/插槽)。
广播了一个事件:$rootScope.$broadcast("clickRow", rowId)
通过任何 parent(甚至 children 控制器)。
然后在你的控制器中你可以像这样处理事件:
$scope.$on("clickRow", function(event, data){
// do a refresh of the view with data == rowId
});
您还可以像这样添加一些日志记录(取自此处:)
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;
});
}
});
我来晚了。但它可以帮助其他人。
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
});
}]
});
我一直在将我的自定义指令升级到新的 component architecture. I've read that components do not support watchers. Is this correct? If so how do you detect changes on an object? For a basic example I have custom component myBox
which has a child component game with a binding on the game . If there is a change game within the game component how do I show an alert message within the myBox? I understand there is rxJS method is it possible to do this purely in angular? My 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-->
$watch
对象在 $scope
对象中可用,因此您需要在控制器工厂函数中添加 $scope
然后将观察者放在变量上。
$scope.$watch(function(){
return myBox.game;
}, function(newVal){
alert('Value changed to '+ newVal)
});
Note: I know you had converted
directive
tocomponent
, to remove dependency of$scope
so that you will get one step closer to Angular2. But it seems like it didn't get removed for this case.
更新
基本上 angular 1.5 确实添加了 .component
方法来区分两种不同的功能。像 component
. 表示通过添加 selector
来执行特定行为,而 directive
表示向 DOM 添加特定行为。指令只是 .directive
DDO(指令定义对象)上的包装方法。只有你能看到的是,他们在使用 .component
方法时删除了 link/compile
函数,你可以在其中获得 angular 编译 DOM.
请使用 Angular 组件生命周期钩子的 $onChanges
/$doCheck
生命周期钩子,这些将在 Angular 1.5.3+ 版本后可用。
$onChanges(changesObj) - Called whenever bindings are updated. The changesObj is a hash whose keys are the names of the bound properties.
$doCheck() - Called on each turn of the digest cycle when binding changes. Provides an opportunity to detect and act on changes.
通过在组件内部使用相同的功能将确保您的代码兼容移动到 Angular 2.
在没有 Watchers
的情况下编写组件此答案概述了五种技术,用于在不使用 watchers.
的情况下编写 AngularJS 1.5 组件- 使用
ng-change
Directive - 使用
$onChanges
Life-cycle Hook - 使用
$doCheck
Life-cycle Hook - 与 require 的组件间通信
- 使用 RxJS 从服务中推送值
使用ng-change
指令
what alt methods available to observe obj state changes without using watch in preparation for 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);
});
这是今后的首选方法。使用$watch
的AngularJS策略是不可扩展的,因为它是一个轮询策略。当 $watch
听众数量达到 2000 左右时,UI 变得迟钝。 Angular 2 中的策略是使框架更具反应性,避免将 $watch
放在 $scope
.
使用$onChanges
生命周期挂钩
使用 1.5.3 版本,AngularJS 将 $onChanges
生命周期挂钩添加到 $compile
服务。
来自文档:
The controller can provide the following methods that act as life-cycle hooks:
- $onChanges(changesObj) - Called whenever one-way (
<
) or interpolation (@
) bindings are updated. ThechangesObj
is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form{ currentValue: ..., previousValue: ... }
. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value.— AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
$onChanges
挂钩用于通过 <
单向绑定对组件的外部更改作出反应。 ng-change
指令用于从具有 &
绑定的组件外部的 ng-model
控制器传播更改。
使用 $doCheck
生命周期挂钩
使用 1.5.8 版本,AngularJS 将 $doCheck
生命周期挂钩添加到 $compile
服务。
来自文档:
The controller can provide the following methods that act as life-cycle hooks:
$doCheck()
- Called on each turn of the digest cycle. Provides an opportunity to detect and act on changes. Any actions that you wish to take in response to the changes that you detect must be invoked from this hook; implementing this has no effect on when$onChanges
is called. For example, this hook could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not be detected by Angular's change detector and thus not trigger$onChanges
. This hook is invoked with no arguments; if detecting changes, you must store the previous value(s) for comparison to the current values.— AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
与 require
的组件间通信
指令可以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 Developer Guide - Intercomponent Communicatation
使用 RxJS
从服务中推送值What about in a situation where you have a Service that's holding state for example. How could I push changes to that Service, and other random components on the page be aware of such a change? Been struggling with tackling this problem lately
使用 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
推送更改。
对于任何对我的解决方案感兴趣的人,我最终求助于 RXJS Observables,当你到达 Angular 2 时你将不得不使用它。这是一个有效的 fiddle 用于之间的通信组件,它让我可以更好地控制观看内容。
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);
}
}
}])
关于使用 ng-change
的小提示,建议使用已接受的答案,以及 angular 1.5 组件。
如果您需要观看 ng-model
和 ng-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>
在 IE11 中可用,MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver。您需要将 $element 服务注入到半中断 DOM/controller 分离的控制器中,但我认为这是 angularjs 中的一个基本异常(即缺陷)。由于 hide/show 是异步的,我们需要 on-show 回调,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']
})
接受的答案非常好,但我可能会补充一点,您也可以使用事件的力量(如果您愿意的话,有点像 Qt 信号/插槽)。
广播了一个事件:$rootScope.$broadcast("clickRow", rowId)
通过任何 parent(甚至 children 控制器)。
然后在你的控制器中你可以像这样处理事件:
$scope.$on("clickRow", function(event, data){
// do a refresh of the view with data == rowId
});
您还可以像这样添加一些日志记录(取自此处:
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;
});
}
});
我来晚了。但它可以帮助其他人。
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
});
}]
});