web-dev-qa-db-fra.com

Les composants AngularJS 1.5+ ne prennent pas en charge Watchers, quel est le problème?

J'ai mis à niveau mes directives personnalisées vers la nouvelle architecture composant . J'ai lu que les composants ne prennent pas en charge les observateurs. Est-ce correct? Si oui, comment détectez-vous les changements sur un objet? Pour un exemple de base, j'ai un composant personnalisé myBox qui a un jeu de composants enfant avec une liaison sur le jeu. S'il y a un jeu de modification dans le composant de jeu, comment puis-je afficher un message d'alerte dans la myBox? Je comprends qu’il existe une méthode rxJS est-il possible de le faire uniquement en angulaire? Mon 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-->
75
Ka Tech

Composants d'écriture sans Observateurs

Cette réponse décrit cinq techniques à utiliser pour écrire des composants AngularJS 1.5 sans utiliser watchers.


Utilisez la directive ng-change

quelles méthodes alternatives sont disponibles pour observer les changements d'état d'obj sans utiliser watch en préparation de AngularJs2?

Vous pouvez utiliser la directive ng-change pour réagir aux modifications d’entrée.

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

Et pour propager l'événement à un composant parent, le gestionnaire d'événements doit être ajouté en tant qu'attribut du composant enfant.

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

Et dans le composant parent:

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

C'est la méthode privilégiée pour l'avenir. La stratégie d'AngularJS consistant à utiliser $watch n'est pas évolutive, car il s'agit d'une stratégie d'interrogation. Lorsque le nombre d'auditeurs $watch atteint environ 2 000, l'interface utilisateur devient lente. La stratégie dans Angular 2 est de rendre le framework plus réactif et d’éviter de placer $watch sur $scope.


Utilisez le crochet $onChanges cycle de vie

Avec version 1.5.3, AngularJS a ajouté le hook $onChanges cycle de vie au service $compile.

De la documentation:

Le contrôleur peut fournir les méthodes suivantes qui agissent comme des crochets de cycle de vie:

  • $ onChanges (changesObj) - Appelé chaque fois que des liaisons à sens unique (<) ou d'interpolation (@) sont mises à jour. La changesObj est un hachage dont les clés sont les noms des propriétés liées qui ont changé et les valeurs sont un objet de la forme { currentValue: ..., previousValue: ... }. Utilisez ce crochet pour déclencher des mises à jour au sein d'un composant, telles que le clonage de la valeur liée afin d'empêcher toute mutation accidentelle de la valeur externe.

- Référence API de la directive détaillée AngularJS - Crochets pour le cycle de vie

Le hook $onChanges permet de réagir aux modifications externes apportées au composant avec des liaisons unidirectionnelles <. La directive ng-change permet de propager les modifications du contrôleur ng-model en dehors du composant avec des liaisons &


Utilisez le crochet $doCheck cycle de vie

Avec version 1.5.8, AngularJS a ajouté le hook $doCheck cycle de vie au service $compile.

De la documentation:

Le contrôleur peut fournir les méthodes suivantes qui agissent comme des crochets de cycle de vie:

  • $doCheck() - Appelé à chaque tour du cycle de digestion. Fournit une opportunité de détecter et d'agir sur les changements. Toutes les actions que vous souhaitez effectuer en réponse aux modifications que vous détectez doivent être appelées à partir de ce point d'ancrage. l'implémentation n'a aucun effet sur le moment où $onChanges est appelé. Par exemple, ce point d'ancrage peut être utile si vous souhaitez effectuer un contrôle d'égalité approfondi ou un objet Date, auquel le détecteur de changement d'Angular ne détectera aucune modification et ne déclenchera donc pas $onChanges. Ce hook est invoqué sans arguments. si vous détectez des modifications, vous devez stocker la ou les valeurs précédentes pour les comparer aux valeurs actuelles.

- Référence API de la directive détaillée AngularJS - Crochets pour le cycle de vie


Communication intercomposant avec require

Les directives peuvent requièrent les contrôleurs d’autres directives pour permettre la communication entre eux. Cela peut être réalisé dans un composant en fournissant un mappage d'objet pour la propriété require . Les clés d'objet spécifient les noms de propriété sous lesquels les contrôleurs requis (valeurs d'objet) seront liés au contrôleur du composant requis.

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

Pour plus d'informations, voir Guide du développeur AngularJS - Communication entre composants


Valeurs Push d'un service avec RxJS

Qu'en est-il dans une situation où vous avez un service en attente, par exemple? Comment puis-je appliquer les modifications à ce service et aux autres composants aléatoires de la page être au courant de ces modifications? Lutte contre ce problème récemment

Construire un service avec RxJS Extensions for 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);
      }
  };
});

Ensuite, abonnez-vous simplement aux modifications.

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

Les clients peuvent s'abonner aux modifications avec DataService.subscribe et les producteurs peuvent les modifier avec DataService.set

La DEMO sur PLNKR .

142
georgeawg

L'objet $watch est disponible dans l'objet $scope. Vous devez donc ajouter $scope dans la fonction de fabrique de votre contrôleur, puis placer l'observateur sur la variable.

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

Demo Here

Remarque: Je sais que vous avez converti directive en component, pour supprimer la dépendance de $scope afin que vous puissiez vous rapprocher de Angulaire2. Mais il semble que cela n’a pas été supprimé pour cette affaire . </ strike>

Mettre à jour

Fondamentalement angulaire 1.5, la méthode .component ajoutée permet de différencier deux fonctionnalités différentes. Comme component. Signifie exécuter un comportement particulier en ajoutant selector, où en tant que directive signifie ajouter un comportement spécifique au DOM. Directive est juste une méthode d'habillage sur .directive DDO (directive Definition Object). Seul ce que vous pouvez voir, ils avaient la fonction remove link/compile lorsqu’ils utilisaient la méthode .component où vous pouviez obtenir un DOM compilé angulaire.

Utilisez le crochet $onChanges$doCheck lifecycle du crochet du cycle de vie des composants angulaires, ceux-ci seront disponibles après la version angulaire 1.5.3+.

_/$ onChanges (changesObj) - Appelé chaque fois que les liaisons sont mises à jour. ChangesObj est un hachage dont les clés sont les noms des propriétés liées.

$ doCheck () - Appelé à chaque tour du cycle de résumé lorsque la liaison est modifiée. Fournit une opportunité de détecter et d'agir sur les changements.

En utilisant la même fonction dans le composant, votre code sera compatible pour passer à Angular 2.

8
Pankaj Parkar

Pour les personnes intéressées par ma solution, je finis par avoir recours aux observables RXJS, que vous devrez utiliser lorsque vous obtiendrez Angular 2. Voici un fiddle efficace pour la communication entre composants qui me permet de mieux contrôler les éléments à surveiller.

Observatoires 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
Ka Tech

Un bref avertissement concernant l'utilisation de ng-change, comme recommandé avec la réponse acceptée, avec une composante angulaire de 1,5.

Si vous devez surveiller un composant dont ng-model et ng-change ne fonctionnent pas, vous pouvez transmettre les paramètres comme suit:

Balisage dans lequel le composant est utilisé:

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

Composant js:

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

Balisage des composants:

<select ng-model="$ctrl.fieldValue"
        ng-change="$ctrl.onChange()">
</select>
2
Wtower

Je suis en retard. Mais cela peut aider un autre peuple.

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
tetra master

Disponible dans IE11, MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver . Vous devez injecter un service $ element dans le contrôleur, ce qui semi-rompt la séparation DOM/contrôleur, mais j'estime qu'il s'agit d'une exception fondamentale (c.-à-d. Un défaut) dans angularjs. Puisque masquer/afficher est asynchrone, nous avons besoin d’un rappel sur l’émission, que angularjs et angular-bootstrap-tab ne fournissent pas. Vous devez également savoir quel élément du DOM vous souhaitez observer. J'ai utilisé le code suivant pour le contrôleur angularjs pour déclencher le reflow du graphique 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']
})
0
user982671

Très bonne réponse acceptée, mais je pourrais ajouter que vous pouvez aussi utiliser le pouvoir des événements (un peu comme dans le signal/les slots Qt si vous voulez).

Un événement est diffusé: $rootScope.$broadcast("clickRow", rowId)de n’importe quel parent (ou même de contrôleur enfants) . Dans votre contrôleur, vous pouvez gérer l’événement de la manière suivante: 

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

Vous pouvez également ajouter une connexion à cela comme ceci (pris à partir d'ici: 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
sebius