Question
I've been upgrading my custom directives to the new [component
architecture](https://docs.angularjs.org/guide/component#component-based-
application-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-->
Answer
Writing Components without
This answer outlines five techniques to use to write AngularJS 1.5 components without using watchers.
- Use the
ng-change
Directive - Use the
$onChanges
Life-cycle Hook - Use the
$doCheck
Life-cycle Hook - Intercomponent Communication with require
- Push Values from a Service with RxJS
Use the ng-change
Directive
what alt methods available to observe obj state changes without using watch in preparation for AngularJs2?
You can use the ng-change
directive to react to input changes.
<textarea ng-model='game.game'
ng-change="game.textChange(game.game)">
</textarea>
And to propagate the event to a parent component, the event handler needs to be added as an attribute of the child component.
<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"
});
And in the parent component:
myBox.gameChange = function(newValue) {
console.log(newValue);
});
This is the preferred method going forward. The AngularJS strategy of using
$watch
is not scalable because it is a polling strategy. When the number of
$watch
listeners reaches around 2000, the UI gets sluggish. The strategy in
Angular 2 is to make the framework more reactive and avoid placing $watch
on
$scope
.
Use the $onChanges
Life-cycle Hook
With version 1.5.3 , AngularJS added the $onChanges
life-cycle hook to
the $compile
service.
From the Docs:
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
The $onChanges
hook is used to react to external changes into the component
with <
one-way bindings. The ng-change
directive is used to propogate
changes from the ng-model
controller outside the component with &
bindings.
Use the $doCheck
Life-cycle Hook
With version 1.5.8 , AngularJS added the $doCheck
life-cycle hook to the
$compile
service.
From the Docs:
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
Intercomponent Communication with require
Directives can require the controllers of other directives to enable communication between each other. This can be achieved in a component by providing an object mapping for the require property. The object keys specify the property names under which the required controllers (object values) will be bound to the requiring component's controller.
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'
});
For more information, see [AngularJS Developer Guide - Intercomponent Communicatation](https://docs.angularjs.org/guide/component#intercomponent- communication)
Push Values from a Service with [RxJS](https://github.com/Reactive-
Extensions/rx.angular.js)
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
Build a service with 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);
}
};
});
Then simply subscribe to the changes.
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();
};
});
Clients can subscribe to changes with DataService.subscribe
and producers
can push changes with DataService.set
.
The DEMO on PLNKR.