Angularjs: 'controller as syntax' e $ watch

153

Como se inscrever na alteração de propriedade ao usar a controller assintaxe?

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
fonte
e quanto a isso. $ watch ()? É válido: this. $ Watch ('name', ...)
João Polo

Respostas:

160

Apenas ligue o contexto relevante.

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

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

ATUALIZAR:

A resposta de Bogdan Gersak é, na verdade, meio equivalente, ambas as respostas tentam ser vinculadas thisao contexto certo. No entanto, achei sua resposta mais limpa.

Dito isso, em primeiro lugar, você precisa entender a ideia subjacente a ela .

ATUALIZAÇÃO 2:

Para quem usa o ES6, ao usar, arrow functionvocê obtém uma função com o contexto correto OOTB.

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

Exemplo

Roy Miloh
fonte
9
Podemos usá-lo sem $ scope para evitar misturar isso e $ scope?
Miron
4
Não, como eu sei, mas está perfeitamente bem. $scopepara você é um tipo de serviço que fornece esse tipo de método.
Roy Miloh 6/06/2014
Você pode esclarecer se nameem return this.name;refere-se ao nome do controlador ou a propriedade " name" aqui?
Jannik Jochem
3
@ Jannik, angular.bindretorna uma função com um contexto delimitado (argumento # 1). No nosso caso, vinculamos this, que é a instância do controlador, à função (arg # 2), portanto, this.namesignifica a propriedade nameda instância do controlador.
Roy Miloh
Eu acho que acabei de entender como isso funciona. Quando a função ligada é chamada, ela simplesmente avalia o valor monitorado, certo?
Jannik Jochem
138

Eu costumo fazer isso:

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
fonte
3
Concordo que esta é a melhor resposta, embora eu acrescentasse que a confusão sobre isso provavelmente está em passar uma função como o primeiro argumento $scope.$watche usar essa função para retornar um valor do fechamento. Ainda tenho que encontrar outro exemplo disso, mas funciona e é o melhor. O motivo pelo qual não escolhi a resposta abaixo (ou seja $scope.$watch('test.name', function (value) {});) é porque exigi que eu codificasse o nome do meu controlador no meu modelo ou no $ stateProvider do ui.router e qualquer alteração ocorreria sem querer interromper o observador.
Morris Cantor
Além disso, a única diferença substantiva entre esta resposta e a resposta atualmente aceita (que usa angular.bind) é se você deseja vincular thisou simplesmente adicionar outra referência ao thisfechamento. Eles são funcionalmente equivalentes e, na minha experiência, esse tipo de escolha costuma ser um chamado subjetivo e o assunto de uma opinião muito forte.
Morris Cantor
1
uma coisa boa sobre o ES6 será a eliminação de ter que executar as 2 soluções alternativas mencionadas para obter o escopo js correto . $scope.$watch( ()=> { return this.name' }, function(){} ) Flecha gorda para o resgate
jusopi
1
você também pode apenas fazer() => this.name
coblr
Você pode fazer isso funcionar $scope.$watchCollectione ainda obter os oldVal, newValparâmetros?
Kraken
23

Você pode usar:

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

Isso está trabalhando o JSFiddle com o seu exemplo.

Artyom Pranovich
fonte
25
O problema com essa abordagem é que o JS agora está confiando no HTML, forçando o controlador a ser vinculado com o mesmo nome (neste caso, "teste") em todos os lugares para que o $ watch funcione. Seria muito fácil introduzir erros sutis.
jsdw
Isso funciona maravilhosamente se você estiver escrevendo Angular 1 como Angular 2, onde tudo é uma diretiva. Object.observe seria incrível agora.
Langdon
13

Semelhante ao uso do "teste" de "TestCtrl como teste", conforme descrito em outra resposta, você pode atribuir a si mesmo seu escopo:

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

Dessa forma, você não está vinculado ao nome especificado no DOM ("TestCtrl como teste") e também evita a necessidade de vincular (isso) a uma função.

... para uso com o 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>
user4389
fonte
Só quero saber uma coisa, ou seja, $scopeé um serviço, por isso, se adicionarmos $scope.self = this, em outro controlador, se fizermos o mesmo, o que acontecerá lá?
Vivek Kumar
12

AngularJs 1.5 suporta o $ ctrl padrão para a estrutura ControllerAs.

$scope.$watch("$ctrl.name", (value) => {
    console.log(value)
});
Niels Steenbeek
fonte
Não funciona para mim ao usar o $ watchGroup, esse limite é conhecido? você pode compartilhar um link para esse recurso, pois não consigo encontrar nada sobre ele?
precisa saber é o seguinte
@ user1852503 Consulte docs.angularjs.org/guide/component Tabela de comparação Diretiva / Definição de componente e verifique o registro 'controllerAs'.
Niels Steenbeek 24/10
Eu entendo agora. Sua resposta é um pouco enganadora. o identificador $ ctrl não se correlaciona com o controlador como um recurso (como $ index faz, por exemplo, em uma repetição de ng), apenas é o nome padrão do controlador dentro de um componente (e a pergunta nem sequer é sobre um componente).
user1852503
@ user1852503 1) O $ ctrl correlaciona o Controller (Controller as) 2) A questão é sobre componentes, uma vez que menciona: "<div ng-controller =" TestCtrl as test ">". 3) Todas as respostas nesta página são de alguma forma iguais à minha resposta. 4) Em relação à documentação, o $ watchGroup deve funcionar bem ao usar $ ctrl.name, pois é baseado em $ watch.
Niels Steenbeek 24/10
2

você pode realmente passar uma função como o primeiro argumento de um $ watch ():

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

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

O que significa que podemos retornar nossa referência 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);
    });
});

Leia um post interessante sobre o controlador Como tópico https://toddmotto.com/digging-into-angulars-controller-as-syntax/

Alexandr
fonte
0

Escrever um $ watch na sintaxe ES6 não foi tão fácil quanto eu esperava. Aqui está o que você pode fazer:

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

NOTA : Isso não funciona quando o View e o Controller são acoplados em uma rota ou através de um objeto de definição de diretiva. O que é mostrado abaixo só funciona quando há um "SomeController as SomeCtrl" no HTML. Assim como Mark V. aponta no comentário abaixo, e assim como ele diz que é melhor fazer como Bogdan faz.

Eu uso: var vm = this;no início do controlador para tirar a palavra "isso" do meu caminho. Então vm.name = 'Max';e no relógio eu return vm.name. Eu uso o "vm" assim como o @Bogdan usa o "self". Essa var, seja "vm" ou "self", é necessária, pois a palavra "this" assume um contexto diferente dentro da função. (portanto, retornar this.name não funcionaria) E sim, você precisa injetar $ scope em sua bela solução "controller as" para alcançar $ watch. Consulte o Guia 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
fonte
11
Isso funciona desde que você tenha "SomeController as vm" no seu HTML. Porém, é enganoso: o "vm.name" na expressão de observação não tem nada a ver com "var vm = this;". A única maneira segura de usar $ watch com "controller as" é passar uma função como o primeiro argumento, como Bogdan ilustra acima.
Mark Visser
-1

Aqui está como você faz isso sem $ scope (e $ watch!) Os 5 principais erros - abuso de relógio

Se você estiver usando a sintaxe "controller as", é melhor e mais limpo evitar o uso do $ scope.

Aqui está o meu código no JSFiddle . (Estou usando um serviço para armazenar o nome, caso contrário, os métodos set e get do ES5 Object.defineProperty causam chamadas 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
fonte
O violino não está funcionando e isso não observará uma propriedade de objeto.
Rootical V. 28/01
@RooticalV. O violino está funcionando. (Certifique-se de que, quando você estiver executando AngualrJS, você especifica o tipo de carga como nowrap-cabeça / nowrap-corpo
Binu Jasim
desculpe, mas eu ainda não conseguiu executá-lo, pena desde a sua solução é muito insterersting
happyZZR1400
@happy Certifique-se de escolher a biblioteca como Angular 1.4. (Não tenho certeza se o 2.0 funcionará) e Tipo de carregamento como Sem quebra automática e pressione Executar. Deveria funcionar.
Binu Jasim