Os componentes do AngularJS 1.5+ não suportam Watchers, qual é o trabalho?

Eu tenho atualizado minhas diretivas personalizadas para a nova arquitetura de componentes . Eu li que os componentes não suportam observadores. Isso está correto? Em caso afirmativo, como você detecta alterações em um object? Para um exemplo básico, tenho o componente personalizado myBox que tem um jogo de componente filho com uma binding no jogo. Se houver um jogo de mudança dentro do componente do jogo, como mostro uma mensagem de alerta dentro do myBox? Eu entendo que há método rxJS é possível fazer isso puramente em angular? Meu 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}}

Escrevendo componentes sem observadores

Esta resposta descreve cinco técnicas para usar para escrever componentes do AngularJS 1.5 sem usar observadores.

  • Use a diretiva ng-change
  • Use o gancho de ciclo de vida $onChanges
  • Use o gancho do ciclo de vida do $doCheck
  • Comunicação de Intercomponente com Requer
  • Empurrar valores de um serviço com o RxJS

Use a diretiva ng-change

Quais methods alternativos disponíveis para observar obj mudanças de estado sem usar o relógio em preparação para AngularJs2?

Você pode usar a diretiva ng-change para reagir às mudanças de input.

  

E para propagar o evento para um componente pai, o manipulador de events precisa ser adicionado como um atributo do componente filho.

  

JS

 app.component("game", { bindings: {game:'=', gameChange: '&'}, controller: function() { var game = this; game.textChange = function (value) { game.gameChange({$value: value}); }); }, controllerAs: 'game', templateUrl: "/template2" }); 

E no componente pai:

 myBox.gameChange = function(newValue) { console.log(newValue); }); 

Este é o método preferido daqui para frente. A estratégia AngularJS de usar $watch não é escalonável porque é uma estratégia de pesquisa. Quando o número de ouvintes de $watch chega por volta de 2000, a interface fica lenta. A estratégia do Angular 2 é tornar o framework mais reativo e evitar colocar o $watch no $scope .


Use o gancho de ciclo de vida $onChanges

Com a versão 1.5.3 , o AngularJS adicionou o gancho de ciclo de vida $onChanges ao serviço $compile .

Do Documentos:

O controlador pode fornecer os seguintes methods que atuam como ganchos do ciclo de vida:

  • $ onChanges (changesObj) – Chamado sempre que ligações unidirecionais ( < ) ou interpolação ( @ ) são atualizadas. O changesObj é um hash cujas chaves são os nomes das propriedades associadas que foram alteradas e os valores são um object da forma { currentValue: ..., previousValue: ... } . Use esse gancho para acionar atualizações em um componente, como clonar o valor associado para evitar a mutação acidental do valor externo.

- AngularJS Comprehensive Directive API Reference - Ganchos do ciclo de vida

O gancho $onChanges é usado para reagir a alterações externas no componente com < ligações unidirecionais. A diretiva ng-change é usada para propagar mudanças do controlador ng-model fora do componente com & bindings.


Use o gancho do ciclo de vida do $doCheck

Com a versão 1.5.8 , o AngularJS adicionou o $doCheck life-cycle ao serviço $compile .

Do Documentos:

O controlador pode fornecer os seguintes methods que atuam como ganchos do ciclo de vida:

  • $doCheck() - Chamado em cada turno do ciclo de digestão. Fornece uma oportunidade para detectar e agir sobre alterações. Quaisquer ações que você deseja executar em resposta às mudanças detectadas devem ser chamadas a partir deste gancho; implementar isso não tem efeito sobre quando $onChanges é chamado. Por exemplo, esse gancho pode ser útil se você deseja executar uma verificação de igualdade profunda ou verificar um object Date, alterações que não seriam detectadas pelo detector de alterações do Angular e, portanto, não acionariam $onChanges . Este gancho é invocado sem argumentos; Se detectar alterações, você deve armazenar o (s) valor (es) anterior (es) para comparação com os valores atuais.

- AngularJS Comprehensive Directive API Reference - Ganchos do ciclo de vida


Comunicação de Intercomponente com require

Diretivas podem exigir que os controladores de outras diretivas permitam a comunicação entre si. Isso pode ser obtido em um componente, fornecendo um mapeamento de object para a propriedade require . As chaves de object especificam os nomes de propriedades sob os quais os controladores necessários (valores de object) serão ligados ao controlador do componente que requer.

 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 obter mais informações, consulte AngularJS Developer Guide - Intercomponent Communicatation


Empurrar valores de um serviço com o RxJS

Que tal em uma situação onde você tem um serviço que está segurando o estado, por exemplo. Como eu poderia enviar alterações para esse serviço, e outros componentes randoms na página devem estar cientes de tal mudança? Tenho lutado para resolver esse problema ultimamente

Construa um serviço com extensões 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); } }; }); 

Então simplesmente assine as mudanças.

 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(); }; }); 

Os clientes podem assinar alterações com DataService.subscribe e os produtores podem enviar alterações com DataService.set .

O DEMO em PLNKR .

$watch object $watch está disponível dentro do object $scope , então você precisa adicionar o $scope dentro da sua function de fábrica do controlador e depois colocar o observador sobre a variável.

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

Demo Here

Nota: Eu sei que você tinha convertido directive para component , para remover a dependência do $scope para que você chegue um passo mais perto do Angular2. Mas parece que não foi removido para este caso.

Atualizar

Basicamente, o angular 1.5 adiciona o método .component jus diferencia duas funcionalidades diferentes. Como component .stands para executar particular behaviby adicionando selector , onde directive significa adicionar um comportamento específico ao DOM. A diretiva é apenas um método de empacotamento no DDO .directive (Diretiva Definição Objeto). Somente o que você pode ver é que eles removeram a function link/compile enquanto usavam o método .component onde você tinha a habilidade de obter um DOM compilado angularmente.

Use o gancho de ciclo de vida $onChanges / $doCheck gancho do ciclo de vida do componente Angular, que estará disponível após a versão Angular 1.5.3+.

$ onChanges (changesObj) – Chamado sempre que as ligações são atualizadas. O changesObj é um hash cujas chaves são os nomes das propriedades associadas.

$ doCheck () – Chamado em cada turno do ciclo de digitação quando a binding é alterada. Fornece uma oportunidade para detectar e agir sobre alterações.

Ao usar a mesma function dentro do componente, seu código será compatível para passar para o Angular 2.

Para qualquer pessoa interessada na minha solução, eu acabo recorrendo ao RXJS Observables, o que você terá que usar quando chegar ao Angular 2. Aqui está um violino de trabalho para as comunicações entre os componentes, isso me dá mais controle sobre o que assistir.

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); } } }]) 

Um pequeno aviso sobre o uso de ng-change , como recomendado com a resposta aceita, junto com um componente angular 1.5.

Caso você precise assistir a um componente que ng-model e ng-change não funcionem, você pode passar os parâmetros como:

Marcação em qual componente é usado:

   

Componente js:

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

Marcação de componente:

  

Disponível no IE11, MutationObserver https://developer.mozilla.org/pt-BR/docs/Web/API/MutationObserver . Você precisa injetar o serviço $ element no controller que semi-quebra a separação DOM / controller, mas eu sinto que esta é uma exceção fundamental (isto é, falha) no angularjs. Como ocultar / mostrar é asynchronous, precisamos de um retorno de chamada em exibição, que angularjs & angular-bootstrap-tab não fornecem. Também requer que você saiba qual elemento DOM específico você deseja observar. Eu usei o código a seguir para o controlador angularjs para acionar o restream do gráfico Highcharts no show.

 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'] }) 

Really Nice aceitou a resposta, mas devo acrescentar que você também pode usar o poder dos events (um pouco como no sinal Qt / slots se você quiser).

Um evento é transmitido: $rootScope.$broadcast("clickRow", rowId) por qualquer pai (ou até mesmo controlador de crianças). Então, no seu controlador, você pode manipular o evento assim:

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

Você também pode adicionar alguns registros como este (a partir daqui: 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; }); } });