AngularJS 1.5+ Componentes no son compatibles con Watchers, ¿cuál es el trabajo?

He estado actualizando mis directivas personalizadas a la nueva architecture de componentes . He leído que los componentes no son compatibles con los observadores. ¿Es esto correcto? Si es así, ¿cómo se detectan cambios en un objeto? Para un ejemplo básico, tengo el componente personalizado myBox que tiene un juego componente infantil con un enlace en el juego. Si hay un juego de cambio dentro del componente del juego, ¿cómo puedo mostrar un mensaje de alerta dentro de myBox? Entiendo que hay un método rxJS ¿es posible hacer esto puramente en angular? Mi JS FIDDLE JS FIDDLE

JS

 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

 
Your Favourite game is: {{myBox.game}}

Change Game
Hi {{name}}

Escritura de componentes sin vigilantes

Esta respuesta describe cinco técnicas para usar para escribir componentes AngularJS 1.5 sin usar vigilantes.

  • Utilice la Directiva ng-change
  • Usa el $onChanges del ciclo de vida de $onChanges
  • Use el $doCheck del ciclo de vida $doCheck
  • La comunicación intercomponente requiere
  • Empujar valores de un servicio con RxJS

Utilice la Directiva ng-change

¿Qué métodos alternativos están disponibles para observar los cambios de estado obj sin usar reloj en preparación para AngularJs2?

Puede usar la directiva ng-change para reactjsr a los cambios de entrada.

  

Y para propagar el evento a un componente principal, el controlador de eventos debe agregarse como un atributo del componente secundario.

  

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 AngularJS de usar $watch no es escalable porque es una estrategia de sondeo. Cuando el número de oyentes de $watch alcanza alrededor de 2000, la IU se vuelve lenta. La estrategia en Angular 2 es hacer que el marco sea más reactivo y evitar colocar $watch en $scope .


Usa el $onChanges del ciclo de vida de $onChanges

Con la versión 1.5.3 , AngularJS agregó el gancho del ciclo de vida $onChanges al servicio $compile .

De los documentos:

El controlador puede proporcionar los siguientes métodos que actúan como ganchos del ciclo de vida:

  • $ onChanges (changesObj): se llama cuando se actualizan enlaces de un sentido ( < ) o de interpolación ( @ ). El changesObj es un hash cuyas claves son los nombres de las propiedades enlazadas que han cambiado, y los valores son un objeto de la forma { currentValue: ..., previousValue: ... } . Utilice este enganche para activar actualizaciones dentro de un componente, como clonar el valor encuadernado para evitar la mutación accidental del valor externo.

- AngularJS Comprehensive Directive API Reference - Anzuelos de ciclo de vida

El gancho $onChanges se usa para reactjsr a cambios externos en el componente con < enlaces unidireccionales. La directiva ng-change se usa para propocionar cambios desde el controlador ng-model fuera del componente con & bindings.


Use el $doCheck del ciclo de vida $doCheck

Con la versión 1.5.8 , AngularJS agregó el gancho del ciclo de vida $doCheck al servicio $compile .

De los documentos:

El controlador puede proporcionar los siguientes métodos que actúan como ganchos del ciclo de vida:

  • $doCheck() : se $doCheck() 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 efecto sobre cuándo se llama $onChanges . Por ejemplo, este enlace podría ser útil si desea realizar una verificación de igualdad profunda, o para verificar un objeto Date, cambios a los que el detector de cambios de Angular no detectaría y, por lo tanto, no $onChanges . Este enganche se invoca sin argumentos; si detecta cambios, debe almacenar los valores anteriores para compararlos con los valores actuales.

- AngularJS Comprehensive Directive API Reference - Anzuelos de ciclo de vida


La comunicación intercomponente require

Las directivas pueden requerir que los controladores de otras directivas permitan la comunicación entre ellos. Esto se puede lograr en un componente proporcionando un mapeo de objetos para la propiedad require . Las claves de objeto especifican los nombres de propiedad bajo los cuales los controladores necesarios (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 AngularJS Developer Guide - Communicatation Intercomponent


Empujar valores de un servicio con RxJS

¿Qué sucede en una situación en la que tiene un Servicio que mantiene el estado, por ejemplo? ¿Cómo podría enviar los cambios a ese Servicio y otros componentes aleatorios en la página estar al tanto de dicho cambio? He estado luchando con abordar este problema últimamente

Crea un servicio con extensiones RxJS para Angular .

    
 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 con DataService.subscribe y los productores pueden impulsar los cambios con DataService.set .

La DEMO en PLNKR .

$watch object está disponible dentro de $scope object, por lo que necesita agregar $scope dentro de la función factory de su controlador y luego ubicar vigilante sobre la variable.

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

Demo aquí

Nota: Sé que había convertido la directive a component para eliminar la dependencia de $scope modo que se acercará un paso más a Angular2. Pero parece que no se eliminó en este caso.

Actualizar

Básicamente angular 1.5 no agrega el método de .component jus diferenciar dos funcionalidades diferentes. El component Like es capaz de realizar un selector adición de comportamiento particular, mientras que la directive es para agregar un comportamiento específico a DOM. La directiva es solo el método de envoltura en .directive DDO (objeto de definición de directiva). Solo lo que puedes ver es que eliminaron la función de link/compile mientras utilizabas el método .component donde tenías la capacidad de obtener un DOM comstackdo angularmente.

$doCheck enganche del ciclo de vida de $onChanges / $doCheck gancho del ciclo de vida del componente angular, que estará disponible después de la versión de Angular 1.5.3+.

$ onChanges (changesObj) : se llama cuando se actualizan enlaces. El changesObj es un hash cuyas claves son los nombres de las propiedades enlazadas.

$ doCheck () : se invoca en cada turno del ciclo de resumen cuando cambia el enlace. Brinda la oportunidad de detectar y actuar sobre los cambios.

Al usar la misma función dentro del componente se asegurará que su código sea compatible para moverse a Angular 2.

Para cualquiera que esté interesado en mi solución, termino recurriendo a RXJS Observables, que tendrá que usar cuando llegue a Angular 2. Este es un violín funcional para la comunicación 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:  {{$ctrl.msg}} Unsubscribe A` }) .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); } } }]) 

Un pequeño aviso sobre el uso de ng-change , como se recomienda con la respuesta aceptada, junto con un componente angular de 1.5.

En caso de que necesite ver un componente que ng-model y ng-change no funcionan, puede pasar los parámetros como:

Marcado en que componente se usa:

   

Componente js:

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

Marcado de componentes:

  

Disponible en IE11, MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver . Necesita inyectar el servicio de $ elementos en el controlador, lo que divide parcialmente la separación DOM / controlador, pero creo que esta es una excepción fundamental (es decir, una falla) en angularjs. Como hide / show es asíncrono, necesitamos callback en el show, que angularjs y angular-bootstrap-tab no proporcionan. También requiere que usted sepa qué elemento DOM específico desea observar. Utilicé el siguiente código para el controlador angularjs para activar el reflujo de la carta de 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'] }) 

La respuesta aceptada fue muy buena, pero podría agregar que también se puede usar la potencia de los eventos (un poco como en la señal Qt / slots si se quiere).

Se transmite un evento: $rootScope.$broadcast("clickRow", rowId) por cualquier padre (o incluso controlador de niños). Luego, en su controlador puede manejar el evento de esta manera:

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

También puede agregar algo de inicio de sesión como este (tomado de aquí: 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; }); } });