Компоненти AngularJS 1.5+ не підтримують Watchers, в чому полягає робота?


78

Я модернізував власні директиви до нової архітектури компонентів . Я читав, що компоненти не підтримують спостерігачів. Це правильно? Якщо так, то як виявити зміни на об’єкті? Для базового прикладу я маю спеціальний компонент, myBoxякий має дочірню гру компонентів з прив'язкою до гри. Якщо в ігровій складовій є гра змін, як показати попереджувальне повідомлення в 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-->

Відповіді:


157

Написання компонентів без спостерігачів

Ця відповідь описує п’ять методів для написання компонентів 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, інтерфейс стає млявим. Стратегія в кутових 2, щоб зробити каркас більш реактивної та уникати розміщення $watchна $scope.


Використовуйте $onChangesгачок життєвого циклу

З версією 1.5.3 , AngularJS додав послугу $onChangesпідключення життєвого циклу $compile.

З Документів:

Контролер може забезпечити наступні методи, які виконують роль гачків життєвого циклу:

  • $ onChanges (changesObj) - Викликається, коли оновляються односторонні ( <) або інтерполяційні ( @) прив'язки. Це changesObjхеш, ключі якого є іменами пов'язаних властивостей, які змінилися, а значення є об'єктом форми { currentValue: ..., previousValue: ... }. Використовуйте цей гачок, щоб запускати оновлення в компоненті, наприклад, клонування зв’язаного значення, щоб запобігти випадковій мутації зовнішнього значення.

- Посилання на вичерпні директиви щодо AngularJS - Гаки життєвого циклу

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


Використовуйте $doCheckгачок життєвого циклу

З версією 1.5.8 , AngularJS додав послугу $doCheckпідключення життєвого циклу $compile.

З Документів:

Контролер може забезпечити наступні методи, які виконують роль гачків життєвого циклу:

  • $doCheck()- Виклик на кожному повороті циклу дайджесту. Надає можливість виявляти зміни та діяти на них. Будь-які дії, які ви хочете зробити у відповідь на виявлені вами зміни, повинні викликатися з цього хука; реалізація цього не впливає на $onChangesвиклик. Наприклад, цей гачок може бути корисним, якщо ви хочете виконати глибоку перевірку рівності або перевірити об'єкт Date, зміни до якого не виявляються детектором змін Angular і, отже, не спрацьовують $onChanges. Цей гачок викликається без аргументів; якщо виявляє зміни, потрібно зберегти попередні значення для порівняння з поточними значеннями.

- Посилання на вичерпні директиви щодо AngularJS - Гаки життєвого циклу


Міжкомпонентне спілкування з 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 для 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 .


Дякую, я прагну реагувати на зміну myBox.game, а не game.gameChange. Оскільки це не вхід, а мітка, вищезазначене може не спрацювати. Я думаю, що, врешті-решт, мені, можливо, доведеться вдатися до rxjs ...
Ka Tech

Я додав інформацію про приєднання подій до батьківських компонентів.
georgeawg

як ця річ впорається із програмною зміною значення myBox.gameзмінної?
Pankaj Parkar

Чудова відповідь @georgeawg. Що Serviceможна сказати про ситуацію, коли у вас є держава, яка тримає, наприклад. Як я можу просувати зміни до цієї Служби, а інші випадкові компоненти на сторінці можуть знати про такі зміни? Останнім часом бореться із вирішенням цієї проблеми ...
Марк Пісак - Trilon.io

1
Хороша відповідь, скажіть лише, що ви можете вдосконалити своє рішення RxJS, використовуючи rx.BehaviourSubject () замість rx.Subject (), який виконує роботу зберігання останнього значення сам по собі, і його можна ініціалізувати за замовчуванням, як і ви до ваших послуг
Мануель Феррейру

8

$watchоб'єкт доступний всередині $scopeоб'єкта, тому вам потрібно додати $scopeвсередину заводської функції контролера, а потім встановити спостерігач над змінною.

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

Демо тут

Примітка: Я знаю, що ви перейшли directiveна component, щоб видалити залежність, $scopeтак що ви наблизитесь на крок ближче до Angular2. Але, здається, у цій справі його не зняли.

Оновлення

В основному angular 1.5 доданий .componentметод jus відрізняє дві різні функції. Подібно componentдо .stands для виконання певної поведінки шляхом додавання selector, де як directiveозначає додавання специфічної поведінки до DOM. Директива - це просто метод обгортки .directiveDDO (об’єкт визначення директиви). Тільки те, що ви можете бачити, це те, що вони мали link/compileфункцію видалення під час використання .componentметоду, де ви мали можливість отримати кутовий скомпільований DOM.

Використовуйте $onChanges/ $doCheckгачок життєвого циклу гачка життєвого циклу компонента, які будуть доступні після версії Angular 1.5.3+.

$ onChanges (changesObj) - Викликається щоразу, коли прив'язки оновлюються. ChangesObj - це хеш, ключі якого є іменами пов'язаних властивостей.

$ doCheck () - Викликається під час кожного обертання циклу дайджесту при зміні прив'язки. Надає можливість виявляти зміни та діяти на них.

Використовуючи ту саму функцію всередині компонента, ви забезпечите сумісність вашого коду для переходу до Angular 2.


Дякую за це, мабуть, дотримуватимусь цього зараз. Чи можете ви вказати мені будь-які посилання, які обговорюють, які альтернативні методи доступні для спостереження за змінами стану obj без використання годинника під час підготовки до AngularJs2?
Ka Tech

1
@KaTech тепер я можу тільки сказати , використовувати observableв RxJS, що було б добре сумісний зAngular2
Pankaj Parkar

4

Для тих, хто цікавиться моїм рішенням, я в кінцевому підсумку вдаюся до 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);
      }
    }
  }])

2

Невеликий опис щодо використання ng-change, як рекомендується разом із прийнятою відповіддю, разом із кутовим компонентом 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>

0

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

Щоб полегшити перехід на Angular2 +, уникайте використання $scopeта $rootScope. Розгляньте можливість використання RxJS замість випромінювачів подій та абонентів.
georgeawg

0

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

Подія транслюється: $rootScope.$broadcast("clickRow", rowId) будь-яким із батьків (або навіть контролером дітей). Тоді у вашому контролері ви можете обробляти подію таким чином:

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

Ви також можете додати деякі журнали щодо цього (взято звідси: 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;
      });
    }
});

2
З Angular 2+ шина подій зникає (шини подій мають проблеми з продуктивністю.) Щоб полегшити перехід на Angular2 +, уникайте використання $scopeта $rootScope. Розгляньте можливість використання RxJS замість випромінювачів подій та абонентів.
georgeawg

0

Я запізнився. Але це може допомогти іншим людям.

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

Ви можете пояснити, чому це працює? Хіба зловживання rootcope не є поганою ідеєю?
mix3d
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.