我一直在将我的自定义指令升级到新的组件架构。我读过组件不支持观察者。这是对的吗?如果是这样,如何检测对象的变化?对于一个基本示例,我有一个自定义组件myBox
它有一个子组件游戏,该游戏在游戏上具有绑定。如果游戏组件中有更改游戏,如何在 myBox 中显示警报消息?我知道有 rxJS 方法是否可以纯粹以角度进行此操作?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-->
在没有观察程序的情况下编写组件
这个答案概述了在不使用观察程序的情况下编写 AngularJS 1.5 组件的五种技术。
- 使用
ng-change
指令 - 使用
$onChanges
生命周期挂钩 - 使用
$doCheck
生命周期挂钩 - 组件间通信与需求
- 使用 RxJS 从服务推送值
使用 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);
});
这是以后的首选方法。使用 $watch
的 AngularJS 策略是不可扩展的,因为它是一种轮询策略。当$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上的演示。
$watch
对象$scope
对象中可用,因此您需要在控制器工厂函数中添加$scope
,然后将观察器放在变量上。
$scope.$watch(function(){
return myBox.game;
}, function(newVal){
alert('Value changed to '+ newVal)
});
在这里演示
注意:我知道您已将
directive
转换为component
,以消除$scope
的依赖,以便您更接近 棱角2.但在这种情况下,它似乎没有被删除。
更新
基本上角度 1.5 确实添加了.component
方法 jus 区分了两种不同的功能。像 component
.stand 执行特定的行为添加selector
,而 directive
代表向 DOM 添加特定行为。 指令只是 DDO(指令定义对象).directive
包装方法。只有你可以看到的是,他们在使用.component
方法时删除了link/compile
函数,你有能力获得角度编译的 DOM。
子的 $onChanges
/$doCheck
生命周期钩子,这些将在 Angular 1.5.3+ 版本之后可用。
$onChanges(changesObj) - 每当绑定更新时调用。changesObj 是一个哈希,其键是绑定属性的名称。
$doCheck() - 当绑定更改时,在摘要循环的每个轮次上调用。提供检测更改并采取行动的机会。
通过在组件中使用相同的功能将确保您的代码兼容以移动到 Angular 2。
对于任何对我的解决方案感兴趣的人,我最终求助于 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);
}
}
}])
关于使用 ng-change
的小提示,建议接受答案,以及角度 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/控制器分离,但我觉得这是 angularjs 中的一个基本例外(即缺陷)。由于hide/show是异步的,我们需要on-show回调,而angularjs和angular-bootstrap-tab不提供。它还要求您知道要观察哪个特定的 DOM 元素。我使用以下代码为 angularjs 控制器触发显示的高图表重排。
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)
由任何父母(甚至子控制器)提供。然后在控制器中,您可以像这样处理事件:
$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;
});
}
});
我迟到了。但它可以帮助另一个人。
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
});
}]
});