Los componentes de AngularJS 1.5+ no son compatibles con Watchers, ¿cuál es la solución?
He estado actualizando mis directivas personalizadas a la nueva arquitectura de componentes . He leído que los componentes no admiten observadores. ¿Es esto correcto? Si es así, ¿cómo se detectan cambios en un objeto? Para un ejemplo básico, tengo un componente personalizado myBox
que tiene un componente secundario de juego con un enlace al juego. Si hay un cambio de juego dentro del componente del juego, ¿cómo muestro un mensaje de alerta dentro de myBox? Entiendo que existe el método rxJS. ¿Es posible hacer esto exclusivamente en angular? Mi 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-->
Componentes de escritura sin observadores
Esta respuesta describe cinco técnicas que se pueden utilizar para escribir componentes de AngularJS 1.5 sin utilizar observadores.
- Utilice la
ng-change
directiva - Utilice el
$onChanges
gancho del ciclo de vida - Utilice el
$doCheck
gancho del ciclo de vida - Comunicación intercomponente con require
- Insertar valores desde un servicio con RxJS
Utilice la ng-change
directiva
¿Qué métodos alternativos están disponibles para observar los cambios de estado de obj sin usar watch en preparación para AngularJs2?
Puede utilizar la ng-change
directiva para reaccionar a los cambios de entrada.
<textarea ng-model='game.game'
ng-change="game.textChange(game.game)">
</textarea>
Y para propagar el evento a un componente principal, el controlador de eventos debe agregarse como un atributo del componente secundario.
<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"
});
Y en el componente principal:
myBox.gameChange = function(newValue) {
console.log(newValue);
});
Este es el método preferido en el futuro. La estrategia de uso de AngularJS $watch
no es escalable porque es una estrategia de sondeo. Cuando el número de $watch
oyentes llega a alrededor de 2000, la interfaz de usuario se vuelve lenta. La estrategia en Angular 2 es hacer que el marco sea más reactivo y evitar $watch
colocarlo $scope
.
Utilice el $onChanges
gancho del ciclo de vida
Con la versión 1.5.3 , AngularJS agregó el $onChanges
enlace de ciclo de vida al $compile
servicio.
De los documentos:
El controlador puede proporcionar los siguientes métodos que actúan como enlaces del ciclo de vida:
- $onChanges(changesObj): se llama cada vez que se actualizan enlaces unidireccionales (
<
) o de interpolación ( ).@
EschangesObj
un hash cuyas claves son los nombres de las propiedades vinculadas que han cambiado y los valores son un objeto de la forma{ currentValue: ..., previousValue: ... }
. Utilice este enlace para activar actualizaciones dentro de un componente, como clonar el valor vinculado para evitar la mutación accidental del valor externo.— Referencia API de directiva integral de AngularJS: enlaces de ciclo de vida
El $onChanges
gancho se utiliza para reaccionar a cambios externos en el componente con <
fijaciones unidireccionales. La ng-change
directiva se utiliza para propagar cambios desde el ng-model
controlador fuera del componente con &
enlaces.
Utilice el $doCheck
gancho del ciclo de vida
Con la versión 1.5.8 , AngularJS agregó el $doCheck
enlace de ciclo de vida al $compile
servicio.
De los documentos:
El controlador puede proporcionar los siguientes métodos que actúan como enlaces del ciclo de vida:
$doCheck()
- Se llama en cada turno del ciclo de resumen. Brinda la oportunidad de detectar y actuar sobre los cambios. Cualquier acción que desee realizar en respuesta a los cambios que detecte debe invocarse desde este enlace; implementar esto no tiene ningún efecto sobre cuándo$onChanges
se llama. Por ejemplo, este gancho podría ser útil si desea realizar una verificación de igualdad profunda o verificar un objeto Fecha cuyos cambios no serían detectados por el detector de cambios de Angular y, por lo tanto, no se activarían$onChanges
. Este gancho se invoca sin argumentos; Si detecta cambios, debe almacenar los valores anteriores para compararlos con los valores actuales.— Referencia API de directiva integral de AngularJS: enlaces de ciclo de vida
Comunicación intercomponente conrequire
Las directivas pueden exigir que los controladores de otras directivas permitan la comunicación entre sí. Esto se puede lograr en un componente proporcionando una asignación de objetos para la propiedad requerida . Las claves de objeto especifican los nombres de propiedad bajo los cuales los controladores requeridos (valores de objeto) se vincularán al controlador del componente requerido.
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'
});
Para obtener más información, consulte la Guía para desarrolladores de AngularJS: comunicación entre componentes
Insertar valores desde un servicio con RxJS
¿Qué pasa en una situación en la que tienes un Servicio que mantiene el estado, por ejemplo? ¿Cómo podría impulsar cambios en ese Servicio y que otros componentes aleatorios de la página estén al tanto de dicho cambio? He estado luchando para abordar este problema últimamente.
Cree un servicio con extensiones RxJS para 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);
}
};
});
Entonces simplemente suscríbete a los cambios.
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();
};
});
Los clientes pueden suscribirse a los cambios DataService.subscribe
y los productores pueden impulsar los cambios con DataService.set
.
La DEMOSTRACIÓN de PLNKR .
$watch
El objeto está disponible dentro $scope
del objeto, por lo que debe agregar $scope
la función de fábrica dentro de su controlador y luego colocar el observador sobre la variable.
$scope.$watch(function(){
return myBox.game;
}, function(newVal){
alert('Value changed to '+ newVal)
});
Demostración aquí
Nota: Sé que se había convertidodirective
acomponent
, para eliminar la dependencia$scope
de modo que pueda acercarse un paso más a Angular2. Pero parece que no se eliminó en este caso.
Actualizar
Básicamente, angular 1.5 agrega .component
un método para diferenciar dos funciones diferentes. Like component
.significa realizar un comportamiento particular agregando selector
, donde as directive
significa agregar un comportamiento específico a DOM. La directiva es solo un método contenedor en .directive
DDO (objeto de definición de directiva). Lo único que puede ver es que tenían link/compile
la función de eliminación mientras usaban .component
un método en el que tenían la capacidad de obtener DOM compilado angular.
Utilice el gancho $onChanges
/ $doCheck
ciclo de vida del gancho del ciclo de vida del componente Angular, estarán disponibles después de la versión Angular 1.5.3+.
$onChanges(changesObj) : se llama cada vez que se actualizan los enlaces. El changeObj es un hash cuyas claves son los nombres de las propiedades vinculadas.
$doCheck() : se llama en cada turno del ciclo de resumen cuando cambia el enlace. Brinda la oportunidad de detectar y actuar sobre los cambios.
El uso de la misma función dentro del componente garantizará que su código sea compatible para pasar a Angular 2.
Para cualquiera interesado en mi solución, termino recurriendo a RXJS Observables, que tendrás que usar cuando llegues a Angular 2. Aquí hay un violín que funciona para las comunicaciones entre componentes, me da más control sobre qué mirar.
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);
}
}
}])