Komponenty AngularJS 1.5+ nie obsługują Watcherów, na czym polega praca?

Uaktualniałem własne dyrektywy do nowejarchitektury komponentów . Czytałem, że komponenty nie obsługują obserwatorów. Czy to prawda? Jeśli tak, jak wykrywasz zmiany na obiekcie? Dla podstawowego przykładu mam własny komponent myBox, który ma komponent potomny gry z wiązaniem na grę . Jeśli w komponencie gry występuje zmiana gry, Jak mogę wyświetlić komunikat alertu w myBox? Rozumiem, że jest metoda rxJS czy można to zrobić czysto kanciaste? My 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-->
Author: GarethAS, 2016-02-21

7 answers

Pisanie komponentów bez obserwatorów

Ta odpowiedź przedstawia pięć technik, których można użyć do napisania komponentów AngularJS 1.5 bez użycia obserwatorów.


Użyj ng-change dyrektywy

Jakie dostępne metody alt do obserwacji zmian stanu obj bez użycia zegarka w przygotowaniu do AngularJs2?

Możesz użyć dyrektywy ng-change, aby reagować na zmiany danych wejściowych.

<textarea ng-model='game.game' 
          ng-change="game.textChange(game.game)">
</textarea>

I aby propagować Zdarzenie do komponentu nadrzędnego, należy dodać obsługę zdarzenia jako atrybut komponentu podrzędnego.

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

I w rodzicu Składnik:

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

Jest to preferowana metoda w przyszłości. Strategia AngularJS użycia $watch nie jest skalowalna, ponieważ jest to strategia ankietowa. Gdy liczba słuchaczy $watch osiągnie około 2000, interfejs użytkownika staje się powolny. Strategia w grze Angular 2 polega na tym, aby framework był bardziej reaktywny i unikał umieszczania $watch na $scope.


Użyj haka $onChanges Life-cycle Hook

Z Wersja 1.5.3 , AngularJS dodał $onChanges life-cycle hook do $compile obsługa.

From the Docs:

[[42]}kontroler może dostarczyć następujące metody, które działają jak haki cyklu życia:
  • $onChanges (changesObj)-wywoływane za każdym razem, gdy aktualizowane są wiązania jednokierunkowe (<) lub interpolacyjne (@). changesObj jest skrótem, którego klucze są nazwami zmienionych właściwości, a wartości są obiektem w postaci { currentValue: ..., previousValue: ... }. Użyj tego Hooka, aby wywołać aktualizacje wewnątrz komponentu, takie jak klonowanie wartości związanej z zapobiec przypadkowej mutacji wartości zewnętrznej.

- AngularJS Comprehensive Directive API Reference -- life-cycle hooks

Hook $onChanges jest używany do reagowania na zewnętrzne zmiany w komponencie za pomocą < wiązań jednokierunkowych. Dyrektywa ng-change służy do propogatowania zmian ze sterownika ng-model poza komponentem za pomocą wiązań &.


Użyj haka $doCheck Life-cycle Hook

Z wersja 1.5.8 , AngularJS dodano hak cyklu życia $doCheck do usługi $compile.

From the Docs:

[[42]}kontroler może dostarczyć następujące metody, które działają jak haki cyklu życia:
  • $doCheck() - wywoływane na każdym zakręcie cyklu trawienia. Umożliwia wykrywanie zmian i reagowanie na nie. Wszelkie działania, które chcesz podjąć w odpowiedzi na wykryte zmiany, muszą być wywoływane z tego Hooka; implementacja tego nie ma wpływu na wywołanie $onChanges. Przykładowo, to hook może być przydatny, jeśli chcesz przeprowadzić głębokie sprawdzenie równości lub sprawdzić obiekt daty, którego zmiany nie zostaną wykryte przez detektor zmiany kąta, a tym samym nie wywołają $onChanges. Ten hook jest wywoływany bez argumentów; w przypadku wykrycia zmian, musisz zapisać poprzednie wartości w celu porównania z bieżącymi wartościami.

- AngularJS Comprehensive Directive API Reference -- life-cycle hooks


Komunikacja Międzykomponentowa z require

Dyrektywy mogąwymagać kontrolerów innych dyrektyw, aby umożliwić komunikację między sobą. Można to osiągnąć w komponencie poprzez mapowanie obiektu dla właściwości require . Klucze obiektów określają nazwy właściwości, pod którymi wymagane Kontrolery (wartości obiektów) będą powiązane z kontrolerem wymagającego komponentu.

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

Aby uzyskać więcej informacji, zobacz AngularJS Developer Guide-Intercomponent Komunikacja


Wypychanie wartości z usługi za pomocą RxJS

A co w sytuacji, gdy masz usługę, która trzyma stan na przykład. Jak mogę wypchnąć zmiany do tej usługi, a inne losowe komponenty na stronie są świadome takiej zmiany? Ostatnio zmagałem się z tym problemem

Zbuduj usługę z rozszerzeniami RxJS dla 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);
      }
  };
});

Następnie wystarczy zapisać się do zmiany.

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

Klienci mogą subskrybować zmiany za pomocą DataService.subscribe, a producenci za pomocą DataService.set.

DEMO na PLNKR.

 158
Author: georgeawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-31 19:30:53

$watch obiekt jest dostępny wewnątrz $scope obiektu, więc musisz dodać $scope wewnątrz funkcji fabrycznej kontrolera i umieścić watcher nad zmienną.

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

Demo Tutaj

Uwaga: wiem, że przekonwertowałeś directive na component, aby usunąć zależność $scope, aby zbliżyć się o krok do Angular2 Ale wygląda na to, że nie został usunięty w tej sprawie.

Update

Zasadniczo angular 1.5 nie dodaje metody .component rozróżnia dwie różne funkcjonalności. Jak component.oznacza wykonanie określonego behaviby adding selector, gdzie as directive oznacza dodanie określonego zachowania do DOM. Dyrektywa jest tylko metodą wrappera na .directive DDO (Directive Definition Object). Jedyne, co możesz zobaczyć, to to, że mieli funkcję Usuń link/compile podczas korzystania z metody .component, w której miałeś możliwość uzyskania kątowego skompilowanego DOM.

Użyj $onChanges/$doCheck lifecycle hook of Angular component lifecycle hook, te będą dostępne po wersji Angular 1.5.3+.

$onChanges (changesObj) - wywoływane zawsze, gdy wiązania są aktualizowane. ChangesObj to hash, którego klucze są nazwami powiązanych właściwości.

$doCheck () - wywoływane na każdym kroku cyklu digest podczas zmiany wiązania. Umożliwia wykrywanie zmian i reagowanie na nie.

Używając tej samej funkcji wewnątrz komponentu zapewni zgodność kodu, aby przejść do kątowej 2.

 8
Author: Pankaj Parkar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-25 12:38:59

Dla wszystkich zainteresowanych moim rozwiązaniem, kończę uciekając się do Rxjs Observables, których będziesz musiał użyć, gdy dojdziesz do Angular 2. Oto działający skrzypek do komunikacji między komponentami, daje mi większą kontrolę nad tym, co oglądać.

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);
      }
    }
  }])
 4
Author: Ka Tech,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-19 07:21:24

Małe ostrzeżenie dotyczące stosowania ng-change, zgodnie z zaleceniami z zaakceptowaną odpowiedzią, wraz ze składnikiem kątowym 1,5.

W przypadku, gdy musisz obserwować komponent, który nie działa ng-model i ng-change, możesz przekazać parametry jako:

Znaczniki, w których składnik jest używany:

<my-component on-change="$ctrl.doSth()"
              field-value="$ctrl.valueToWatch">
</my-component>

Składnik js:

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

Znaczniki komponentu:

<select ng-model="$ctrl.fieldValue"
        ng-change="$ctrl.onChange()">
</select>
 2
Author: Wtower,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-03 15:23:57

Dostępny w IE11, MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver . musisz wprowadzić usługę $ element do kontrolera, która częściowo łamie separację DOM/kontroler, ale uważam, że jest to podstawowy wyjątek (np. wada) w angularjs. Ponieważ hide / show jest asynchroniczny, potrzebujemy on-show callback, że angularjs & angular-Bootstrap-tab nie zapewniają. Wymaga również, aby u wiedział, który konkretny element DOM chcesz obserwować. Użyłem poniższego kodu do angularjs kontroler do wyzwalania Highcharts chart reflow 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']
})
 0
Author: user982671,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-30 18:10:01

Naprawdę ładna, przyjęta odpowiedź, ale dodam, że można również użyć mocy zdarzeń(trochę jak w Qt signal / slots jeśli chcesz ).

Transmitowane jest Wydarzenie: $rootScope.$broadcast("clickRow", rowId) przez każdego rodzica ( a nawet dzieci). Następnie w kontrolerze możesz obsłużyć Zdarzenie w następujący sposób:

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

Możesz też dodać trochę logowania w ten sposób ( wzięte stąd : 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;
      });
    }
});
 0
Author: sebius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-03 08:58:26

Jestem spóźniona. Ale może pomóc innym ludziom.

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
        });
    }]
});
 0
Author: tetra master,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-05 16:50:50