Angularjs: 'controlador como sintaxis' y $ watch

153

¿Cómo suscribirse al cambio de propiedad cuando se usa la controller assintaxis?

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
fuente
¿Qué pasa con esto. $ watch ()? Es válido: este. $ Watch ('nombre', ...)
Joao Polo

Respuestas:

160

Solo vincula el contexto relevante.

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

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

ACTUALIZAR:

La respuesta de Bogdan Gersak es realmente equivalente, ambas respuestas intentan vincularse thiscon el contexto correcto. Sin embargo, encontré su respuesta más limpia.

Dicho esto, en primer lugar, debe comprender la idea subyacente detrás de esto .

ACTUALIZACIÓN 2:

Para aquellos que usan ES6, al usarlo arrow functionobtienes una función con el contexto correcto OOTB.

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

Ejemplo

Roy Miloh
fuente
9
¿Podemos usarlo sin $ scope para evitar la combinación de esto y $ scope?
Miron
44
No, como sé, pero está perfectamente bien. $scopepara usted es un tipo de servicio que proporciona este tipo de métodos.
Roy Miloh
¿Puede aclarar si nameen se return this.name;refiere al nombre del controlador o la propiedad " name" aquí?
Jannik Jochem
3
@ Jannik, angular.binddevuelve una función con un contexto acotado (arg # 1). En nuestro caso, vinculamos this, que es la instancia del controlador, a la función (arg # 2), por lo que this.namesignifica la propiedad namede la instancia del controlador.
Roy Miloh
Creo que acabo de entender cómo funciona esto. Cuando se llama a la función enlazada, simplemente se evalúa al valor observado, ¿verdad?
Jannik Jochem
138

Usualmente hago esto:

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 Napoli
fuente
3
Estoy de acuerdo en que esta es la mejor respuesta, aunque agregaría que la confusión sobre esto probablemente esté en pasar una función como primer argumento $scope.$watchy usar esa función para devolver un valor del cierre. Todavía tengo que encontrar otro ejemplo de esto, pero funciona y es el mejor. La razón por la que no elegí la respuesta a continuación (es decir, $scope.$watch('test.name', function (value) {});) es porque requería que codificara lo que denominé mi controlador en mi plantilla o en $ stateProvider de ui.router y cualquier cambio que se produzca rompería el observador sin darse cuenta.
Morris Singer
Además, la única diferencia sustancial entre esta respuesta y la respuesta actualmente aceptada (que utiliza angular.bind) es si desea vincularse thiso simplemente agregar otra referencia thisdentro del cierre. Estos son funcionalmente equivalentes, y, en mi experiencia, este tipo de elección es a menudo una llamada subjetiva y el asunto de una opinión muy fuerte.
Morris Singer
1
Una cosa buena acerca de ES6 será la eliminación de tener que hacer las 2 soluciones antes mencionadas para obtener el alcance js correcto . $scope.$watch( ()=> { return this.name' }, function(){} ) Flecha gorda al rescate
jusopi
1
también puedes hacerlo() => this.name
coblr
¿Puedes hacer que esto funcione $scope.$watchCollectiony aún obtener los oldVal, newValparámetros?
Kraken
23

Puedes usar:

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

Esto está trabajando JSFiddle con su ejemplo.

Artyom Pranovich
fuente
25
El problema con este enfoque es que el JS ahora depende del HTML, lo que obliga al controlador a vincularse con el mismo nombre (en este caso, "prueba") en todas partes para que el $ watch funcione. Sería muy fácil introducir errores sutiles.
jsdw
Esto funciona maravillosamente si estás escribiendo Angular 1 como Angular 2 donde todo es una directiva. Object.observe sería increíble ahora.
Langdon
13

De manera similar al uso de la "prueba" de "TestCtrl como prueba", como se describe en otra respuesta, puede asignar "self" a su alcance:

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 esta manera, no está vinculado al nombre especificado en el DOM ("TestCtrl como prueba") y también evita la necesidad de vincular (esto) a una función.

... para usar con el html original especificado:

<div ng-controller="TestCtrl as test">
    <input type="text" ng-model="test.name" />
    <a ng-click="test.changeName()" href="#">Change Name</a>
</div>
usuario4389
fuente
Solo quiero saber una cosa, es decir, $scopees un servicio, así que si agregamos $scope.self = this, en otro controlador si hacemos lo mismo, ¿qué pasará allí?
Vivek Kumar el
12

AngularJs 1.5 admite el valor predeterminado $ ctrl para la estructura ControllerAs.

$scope.$watch("$ctrl.name", (value) => {
    console.log(value)
});
Niels Steenbeek
fuente
No funciona para mí cuando uso $ watchGroup, ¿es este un límite conocido? ¿Puedes compartir un enlace a esta función ya que no puedo encontrar nada al respecto?
user1852503
@ user1852503 Ver docs.angularjs.org/guide/component Tabla de comparación Directiva / Definición de componente y verificar el registro 'controllerAs'.
Niels Steenbeek
Entiendo ahora. Tu respuesta es un poco engañosa. el identificador $ ctrl no se correlaciona con el controlador como una característica (como $ index lo hace, por ejemplo, en una repetición ng), simplemente resulta ser el nombre predeterminado para el controlador dentro de un componente (y la pregunta ni siquiera es acerca de un componente).
user1852503
@ user1852503 1) El $ ctrl correlaciona el Controlador (Controlador como) 2) La pregunta es acerca de los componentes, ya que menciona: "<div ng-controller =" TestCtrl como prueba ">". 3) Todas las respuestas en esta página son de alguna manera las mismas que mi respuesta. 4) Con respecto a la documentación, $ watchGroup debería funcionar bien cuando se usa $ ctrl.name, ya que se basa en $ watch.
Niels Steenbeek
2

en realidad puede pasar una función como primer argumento de un $ watch ():

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

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

Lo que significa que podemos devolver nuestra referencia 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);
    });
});

Lea una publicación interesante sobre el tema controllerAs https://toddmotto.com/digging-into-angulars-controller-as-syntax/

Alexandr
fuente
0

Escribir un $ watch en la sintaxis de ES6 no fue tan fácil como esperaba. Esto es lo que puedes hacer:

// 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
fuente
-1

NOTA : Esto no funciona cuando View y Controller están acoplados en una ruta o a través de un objeto de definición de directiva. Lo que se muestra a continuación solo funciona cuando hay un "SomeController as SomeCtrl" en el HTML. Al igual que Mark V. señala en el comentario a continuación, y tal como él dice, es mejor hacer lo que Bogdan hace.

Yo uso: var vm = this;al comienzo del controlador para quitar la palabra "this" de mi camino. Entonces vm.name = 'Max';y en el reloj yo return vm.name. Yo uso el "vm" al igual que @Bogdan usa "self". Esta var, ya sea "vm" o "self", es necesaria ya que la palabra "this" toma un contexto diferente dentro de la función. (por lo que devolver this.name no funcionaría) Y sí, debe inyectar $ scope en su hermosa solución "controlador como" para llegar a $ watch. Consulte la Guía de estilo 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
fuente
11
Esto funciona siempre que tenga "SomeController as vm" en su HTML. Sin embargo, es engañoso: el "vm.name" en la expresión watch no tiene nada que ver con "var vm = this;". La única forma segura de usar $ watch con "controller as" es pasar una función como primer argumento, como ilustra Bogdan arriba.
Mark Visser
-1

Así es como se hace esto sin $ scope (y $ watch!) Los 5 errores principales: abuso de reloj

Si está utilizando la sintaxis "controlador como", es mejor y más limpio evitar usar $ scope.

Aquí está mi código en JSFiddle . (Estoy usando un servicio para mantener el nombre; de ​​lo contrario, el conjunto ES5 Object.defineProperty y los métodos get causan llamadas infinitas.

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
fuente
El violín no funciona y esto no observará una propiedad de objeto.
Rootical V.
@RooticalV. El violín está funcionando. (Asegúrese de que cuando esté ejecutando AngualrJS, especifique el tipo de carga como nowrap-head / nowrap-body
Binu Jasim el
lo siento pero aún no logré ejecutarlo, lástima ya que su solución es muy poco interesante
happyZZR1400
@happy Asegúrate de elegir la biblioteca como Angular 1.4. (No estoy seguro de si 2.0 funcionará) y Tipo de carga como Sin ajuste, y presione Ejecutar. Deberia de funcionar.
Binu Jasim