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>  
Miron
la source
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

Roy Miloh
la source
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)
   });
});
Nico Naples
la source
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.

Artyom Pranovich
la source
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>
user4389
la source
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)
});
Niels Steenbeek
la source
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/

Alexandr
la source
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);
    }
  }
}
Maciej Gurban
la source
-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);
    });
}
wojjas
la source
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);
        }
    }); 
});
Binu Jasim
la source
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