Angularjs: 'controller as syntax' et $ watch


153

Comment s'abonner au changement de propriété lors de l'utilisation de la controller assyntaxe?

controller('TestCtrl', function ($scope) {
  this.name = 'Max';
  this.changeName = function () {
    this.name = new Date();
  }
  // not working       
  $scope.$watch("name",function(value){
    console.log(value)
  });
});
<div ng-controller="TestCtrl as test">
  <input type="text" ng-model="test.name" />
  <a ng-click="test.changeName()" href="#">Change Name</a>
</div>  

qu'en est-il de ça. $ watch ()? C'est valide: this. $ Watch ('name', ...)
Joao Polo

Réponses:


160

Liez simplement le contexte pertinent.

$scope.$watch(angular.bind(this, function () {
  return this.name;
}), function (newVal) {
  console.log('Name changed to ' + newVal);
});

Exemple: http://jsbin.com/yinadoce/1/edit

METTRE À JOUR:

La réponse de Bogdan Gersak est en fait un peu équivalente, les deux réponses essaient de se lier thisavec le bon contexte. Cependant, j'ai trouvé sa réponse plus propre.

Cela dit, vous devez d'abord et avant tout comprendre l'idée sous-jacente .

MISE À JOUR 2:

Pour ceux qui utilisent ES6, en utilisant arrow functionvous obtenez une fonction avec le bon contexte OOTB.

$scope.$watch(() => this.name, function (newVal) {
  console.log('Name changed to ' + newVal);
});

Exemple


9
Pouvons-nous l'utiliser sans $ scope pour éviter de mélanger ceci et $ scope?
Miron

4
Non, je sais, mais c'est parfaitement bien. $scopepour vous est une sorte de service qui fournit ce genre de méthodes.
Roy Miloh

Pouvez-vous préciser si namedans return this.name;fait référence au nom du contrôleur ou à la propriété " name" ici?
Jannik Jochem

3
@Jannik, angular.bindrenvoie une fonction avec un contexte borné (arg # 1). Dans notre cas, nous lions this, qui est l'instance du contrôleur, à la fonction (arg # 2), donc this.namesignifie la propriété namede l'instance du contrôleur.
Roy Miloh

Je pense que je viens de comprendre comment cela fonctionne. Lorsque la fonction liée est appelée, elle évalue simplement la valeur surveillée, non?
Jannik Jochem

138

Je fais généralement ceci:

controller('TestCtrl', function ($scope) {
    var self = this;

    this.name = 'Max';
    this.changeName = function () {
        this.name = new Date();
   }

   $scope.$watch(function () {
       return self.name;
   },function(value){
        console.log(value)
   });
});

3
Je conviens que c'est la meilleure réponse, même si j'ajouterais que la confusion à ce sujet réside probablement dans le passage d'une fonction comme premier argument $scope.$watchet l'utilisation de cette fonction pour renvoyer une valeur à partir de la fermeture. Je n'ai pas encore rencontré un autre exemple de cela, mais cela fonctionne et c'est le meilleur. La raison pour laquelle je n'ai pas choisi la réponse ci-dessous (c'est-à-dire $scope.$watch('test.name', function (value) {});) est que je dois coder en dur ce que j'ai nommé mon contrôleur dans mon modèle ou dans $ stateProvider de ui.router et tout changement dans ce dernier briserait par inadvertance l'observateur.
Morris Singer

En outre, la seule différence de fond entre cette réponse et la réponse actuellement acceptée (qui utilise angular.bind) est de savoir si vous souhaitez lier thisou simplement ajouter une autre référence à l' thisintérieur de la clôture. Celles-ci sont fonctionnellement équivalentes et, d'après mon expérience, ce type de choix est souvent un appel subjectif et la question d'une opinion très forte.
Morris Singer

1
une bonne chose à propos d'ES6 sera d'éliminer d'avoir à faire les 2 solutions de contournement susmentionnées pour obtenir la bonne portée js . $scope.$watch( ()=> { return this.name' }, function(){} ) Grosse flèche à la rescousse
jusopi

1
vous pouvez aussi le faire() => this.name
coblr

Pouvez-vous faire fonctionner cela $scope.$watchCollectionet obtenir les oldVal, newValparamètres?
Kraken

23

Vous pouvez utiliser:

   $scope.$watch("test.name",function(value){
        console.log(value)
   });

Cela fonctionne JSFiddle avec votre exemple.


25
Le problème avec cette approche est que le JS s'appuie maintenant sur le HTML, forçant le contrôleur à être lié sous le même nom (dans ce cas "test") partout pour que la $ watch fonctionne. Serait très facile d'introduire des bogues subtils.
jsdw du

Cela fonctionne à merveille si vous écrivez Angular 1 comme Angular 2 où tout est une directive. Object.observe serait cependant incroyable pour le moment.
Langdon

13

Similaire à l'utilisation du "test" de "TestCtrl as test", comme décrit dans une autre réponse, vous pouvez attribuer "self" votre champ d'application:

controller('TestCtrl', function($scope){
    var self = this;
    $scope.self = self;

    self.name = 'max';
    self.changeName = function(){
            self.name = new Date();
        }

    $scope.$watch("self.name",function(value){
            console.log(value)
        });
})

De cette façon, vous n'êtes pas lié au nom spécifié dans le DOM ("TestCtrl as test") et vous évitez également de devoir lier (this) à une fonction.

... à utiliser avec le code HTML d'origine spécifié:

<div ng-controller="TestCtrl as test">
    <input type="text" ng-model="test.name" />
    <a ng-click="test.changeName()" href="#">Change Name</a>
</div>

Je veux juste savoir une chose, c'est à dire, $scopeest un service, donc si nous ajoutons $scope.self = this, puis dans un autre contrôleur si nous faisons la même chose, que se passera-t-il?
Vivek Kumar

12

AngularJs 1.5 prend en charge la valeur par défaut $ ctrl pour la structure ControllerAs.

$scope.$watch("$ctrl.name", (value) => {
    console.log(value)
});

Cela ne fonctionne pas pour moi lorsque vous utilisez $ watchGroup, est-ce une limite connue? pouvez-vous partager un lien vers cette fonctionnalité car je ne trouve rien à ce sujet.
user1852503

@ user1852503 Voir docs.angularjs.org/guide/component Tableau de comparaison Directive / Définition de composant et vérifier l'enregistrement 'controllerAs'.
Niels Steenbeek

Je comprends maintenant. Votre réponse est un peu trompeuse. l'identifiant $ ctrl n'est pas en corrélation avec le contrôleur en tant que fonctionnalité (comme $ index le fait par exemple dans un ng-repeat), il se trouve juste qu'il s'agit du nom par défaut du contrôleur à l'intérieur d'un composant (et la question ne concerne même pas un composant).
user1852503

@ user1852503 1) Le $ ctrl met en corrélation le Controller (Controller as) 2) La question concerne les composants, puisqu'elle mentionne: "<div ng-controller =" TestCtrl as test ">". 3) Toutes les réponses sur cette page sont en quelque sorte les mêmes que ma réponse. 4) En ce qui concerne la documentation, $ watchGroup devrait fonctionner correctement avec $ ctrl.name car il est basé sur $ watch.
Niels Steenbeek

2

vous pouvez en fait passer une fonction comme premier argument d'un $ watch ():

 app.controller('TestCtrl', function ($scope) {
 this.name = 'Max';

// hmmm, a function
 $scope.$watch(function () {}, function (value){ console.log(value) });
 });

Ce qui signifie que nous pouvons renvoyer notre référence this.name:

app.controller('TestCtrl', function ($scope) {
    this.name = 'Max';

    // boom
    $scope.$watch(angular.bind(this, function () {
    return this.name; // `this` IS the `this` above!!
    }), function (value) {
      console.log(value);
    });
});

Lisez un article intéressant sur ControllerAs topic https://toddmotto.com/digging-into-angulars-controller-as-syntax/



0

Écrire une $ watch dans la syntaxe ES6 n'a pas été aussi facile que je m'y attendais. Voici ce que vous pouvez faire:

// Assuming
// controllerAs: "ctrl"
// or
// ng-controller="MyCtrl as ctrl"
export class MyCtrl {
  constructor ($scope) {
    'ngInject';
    this.foo = 10;
    // Option 1
    $scope.$watch('ctrl.foo', this.watchChanges());
    // Option 2
    $scope.$watch(() => this.foo, this.watchChanges());
  }

  watchChanges() {
    return (newValue, oldValue) => {
      console.log('new', newValue);
    }
  }
}

-1

REMARQUE : cela ne fonctionne pas lorsque View et Controller sont couplés dans une route ou via un objet de définition de directive. Ce qui est montré ci-dessous ne fonctionne que lorsqu'il y a un "SomeController as SomeCtrl" dans le HTML. Tout comme Mark V. le souligne dans le commentaire ci-dessous, et comme il le dit, il vaut mieux faire comme Bogdan le fait.

J'utilise: var vm = this;au début du contrôleur pour me débarrasser du mot «ceci». Ensuite , vm.name = 'Max';et dans la montre que je return vm.name. J'utilise le "vm" comme @Bogdan utilise "self". Cette variable, que ce soit "vm" ou "self" est nécessaire car le mot "this" prend un contexte différent à l'intérieur de la fonction. (donc retourner this.name ne fonctionnerait pas) Et oui, vous devez injecter $ scope dans votre belle solution "controller as" afin d'atteindre $ watch. Voir le guide de style de John Papa: https://github.com/johnpapa/angularjs-styleguide#controllers

function SomeController($scope, $log) {
    var vm = this;
    vm.name = 'Max';

    $scope.$watch('vm.name', function(current, original) {
        $log.info('vm.name was %s', original);
        $log.info('vm.name is now %s', current);
    });
}

11
Cela fonctionne tant que vous avez "SomeController as vm" dans votre HTML. C'est trompeur, cependant: le "vm.name" dans l'expression de surveillance n'a rien à voir avec "var vm = this;". Le seul moyen sûr d'utiliser $ watch avec "controller as" est de passer une fonction comme premier argument, comme Bogdan l'illustre ci-dessus.
Mark Visser

-1

Voici comment faire cela sans $ scope (et $ watch!) Top 5 des erreurs - Abuser de la montre

Si vous utilisez la syntaxe "controller as", il vaut mieux éviter d'utiliser $ scope.

Voici mon code dans JSFiddle . (J'utilise un service pour contenir le nom, sinon l'ensemble des méthodes ES5 Object.defineProperty et get provoquent des appels infinis.

var app = angular.module('my-module', []);

app.factory('testService', function() {
    var name = 'Max';

    var getName = function() {
        return name;
    }

    var setName = function(val) {
        name = val;
    }

    return {getName:getName, setName:setName};
});

app.controller('TestCtrl', function (testService) {
    var vm = this;

    vm.changeName = function () {
        vm.name = new Date();
    }

    Object.defineProperty(this, "name", {
        enumerable: true,
        configurable: false,
        get: function() {
            return testService.getName();
        },
        set: function (val) {
            testService.setName(val);
            console.log(vm.name);
        }
    }); 
});

Le violon ne fonctionne pas et cela n'observera pas une propriété d'objet.
Rootical V.28

@RooticalV. Le violon fonctionne. (Assurez-vous que lorsque vous exécutez AngualrJS, vous spécifiez le type de charge comme nowrap-head / nowrap-body
Binu Jasim

désolé mais je n'ai toujours pas réussi à l'exécuter, dommage car votre solution est très intéressante
happyZZR1400

@happy Assurez-vous de choisir la bibliothèque Angular 1.4. (Je ne suis pas sûr si 2.0 fonctionnera) et le type de charge comme No wrap, et appuyez sur Exécuter. Ça devrait marcher.
Binu Jasim
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.