AngularJS 1. 5+ Компоненты не поддерживают Watchers, что обходится?

Я обновлял свои пользовательские директивы до новой архитектуры компонентов. Я читал, что компоненты не поддерживают наблюдателей. Это правильно? Если так, как вы обнаруживаете изменения на объекте? Для базового примера у меня есть пользовательский компонент myBox который имеет дочерний компонент game с привязкой к игре. Если в игровом компоненте есть игра с изменениями, как мне показать предупреждение в myBox? Я так понимаю, есть метод rxJS, возможно ли сделать это чисто по-угловому? Мой 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-->

Ответ 1

Написание компонентов без Наблюдатели

В этом ответе описываются пять методов, используемых для написания компонентов AngularJS 1.5 без использования наблюдателей.


Используйте директиву ng-change

какие альт-методы доступны для наблюдения изменений состояния obj без использования часов для подготовки к 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);
});

Это предпочтительный метод в будущем. Стратегия использования AngularJS $watch не является масштабируемой, поскольку она является стратегией опроса. Когда количество слушателей $watch достигает около 2000, пользовательский интерфейс становится вялым. Стратегия в Angular 2 - сделать структуру более реактивной и не размещать $watch на $scope.


Используйте $onChanges Крюк жизненного цикла

С версией 1.5.3, AngularJS добавил $onChanges привязку жизненного цикла к службе $compile.

Из Документов:

Контроллер может предоставить следующие методы, которые действуют как крючки жизненного цикла:

  • $onChanges (changesObj) - Вызывается каждый раз, когда обновляются односторонние (<) или интерполяционные привязки (@). changesObj является хешем, ключи которого являются именами связанных свойств, которые изменились, а значения являются объектом формы { currentValue: ..., previousValue: ... }. Используйте этот крючок для запуска обновлений внутри компонента, например клонирования связанного значения, чтобы предотвратить случайную мутацию внешнего значения.

- Справочная информация по API-интерфейсу AngularJS - крюки жизненного цикла

Крючок $onChanges используется для реагирования на внешние изменения в компоненте с < односторонними связями. Директива ng-change используется для распространения изменений с контроллера ng-model вне компонента с привязками &.


Используйте $doCheck Крюк жизненного цикла

С версией 1.5.8, AngularJS добавил $doCheck привязку жизненного цикла к сервису $compile.

Из Документов:

Контроллер может предоставить следующие методы, которые действуют как крючки жизненного цикла:

  • $doCheck() - Вызывается каждый оборот цикла дайджеста. Предоставляет возможность обнаруживать изменения и вносить изменения в них. Любые действия, которые вы хотите предпринять в ответ на обнаруженные вами изменения, должны быть вызваны из этого крючка; реализация этого не влияет на то, когда вызывается $onChanges. Например, этот крючок может быть полезен, если вы хотите выполнить глубокую проверку равенства или проверить объект Date, изменения которого не будут обнаружены с помощью детектора изменений Angular и, следовательно, не запускать $onChanges. Этот крючок вызывается без аргументов; при обнаружении изменений вы должны сохранить предыдущие значения для сравнения с текущими значениями.

- Справочная информация по API-интерфейсу AngularJS - крюки жизненного цикла


Компонентная связь с require

Директивы могут 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 для 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.

DEMO на PLNKR.

Ответ 2

$watch объект доступен внутри объекта $scope, поэтому вам нужно добавить $scope внутри вашей функции управления factory, а затем поместить наблюдателя на переменную.

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

Демо здесь

Примечание: Я знаю, что вы преобразовали directive в component, чтобы удалить зависимость $scope, чтобы вы приблизились к шагу ближе к Angular2. Но, похоже, он не удалился по этому делу.Забастовкa >

Обновление

В основном angular 1.5 добавлен метод .component jus различает две разные функции. Подобно component.stands, чтобы выполнить конкретное поведение, добавляя selector, где as directive означает добавление определенного поведения в DOM. Директива - это всего лишь метод обертки на .directive DDO (объект определения директивы). Только то, что вы можете видеть, они удалили функцию link/compile при использовании метода .component, где у вас была возможность получить angular скомпилированный DOM.

Использовать крючок жизненного цикла $onChanges/$doCheck привязки жизненного цикла компонента angular, они будут доступны после версии angular 1.5.3+.

$onChanges (changesObj) - Вызывается, когда привязки обновляются. ИзмененияObj - хэш, ключи которого являются именами связанных свойств.

$doCheck() - вызывается при каждом повороте цикла дайджеста при изменении привязки. Предоставляет возможность обнаруживать и действовать с изменениями.

Используя ту же самую функцию внутри компонента, вы убедитесь, что ваш код будет совместим для перехода на angular 2.

Ответ 3

Для всех, кто интересуется моим решением, я в конечном итоге прибегаю к RXJS Observables, что вам нужно будет использовать, когда вы перейдете к Angular 2. Вот рабочая скрипка для связи между компонентами, это дает мне больше контроля над что посмотреть.

JS FIDDLE RXJS Наблюдения

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

Ответ 4

Небольшой хедз-ап в отношении использования ng-change, как рекомендовано с принятым ответом, вместе с компонентом angular 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>

Ответ 5

Доступно в IE11, MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver. Вам нужно ввести $element service в контроллер, который разрывает разделение DOM/контроллера, но я чувствую, что это фундаментальное исключение (т.е. Дефект) в angularjs. Поскольку hide/show является асинхронным, нам нужен обратный вызов on-show, что угловые и angular -bootstrap-tab не предоставляют. Это также требует, чтобы вы знали, какой конкретный элемент DOM вы хотите наблюдать. Я использовал следующий код для контроллера angularjs, чтобы запустить отображение диаграммы Highcharts в режиме on-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']
})

Ответ 6

Действительно хороший принятый ответ, но я мог бы добавить, что вы также можете использовать силу событий (немного похоже на сигнал/слоты Qt, если хотите).

Событие транслируется: $rootScope.$broadcast("clickRow", rowId) любым родительским (или даже $rootScope.$broadcast("clickRow", rowId) контроллером). Затем в вашем контроллере вы можете обработать событие следующим образом:

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

Вы также можете добавить некоторые записи на эту тему (взято отсюда: fooobar.com/questions/2210782/...)

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

Ответ 7

Я опоздал. Но это может помочь другим людям.

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