Angularjs: 'controller as syntax' e $ watch


153

Come iscriversi al cambio di proprietà quando si utilizza la controller assintassi?

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>  

che dire di questo. $ watch ()? È valido: questo. $ Watch ('name', ...)
Joao Polo

Risposte:


160

Basta associare il contesto rilevante.

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

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

AGGIORNARE:

La risposta di Bogdan Gersak è in realtà un po 'equivalente, entrambe le risposte cercano di legare thiscon il giusto contesto. Tuttavia, ho trovato la sua risposta più pulita.

Detto questo, prima di tutto, devi capire l'idea che sta alla base .

AGGIORNAMENTO 2:

Per coloro che usano ES6, usando arrow functionsi ottiene una funzione con il giusto contesto OOTB.

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

Esempio


9
Possiamo usarlo senza $ scope per evitare il mix di questo e $ scope?
Miron,

4
No come lo so, ma va benissimo. $scopeper te è un tipo di servizio che fornisce questo tipo di metodi.
Roy Miloh,

Puoi chiarire se namein si return this.name;riferisce al nome del controller o alla proprietà " name" qui?
Jannik Jochem,

3
@Jannik, angular.bindrestituisce una funzione con un contesto limitato (arg # 1). Nel nostro caso, associamo this, che è l'istanza del controller, alla funzione (arg # 2), quindi this.namesignifica la proprietà namedell'istanza del controller.
Roy Miloh,

Penso di aver appena capito come funziona. Quando viene chiamata la funzione associata, viene semplicemente valutata il valore osservato, giusto?
Jannik Jochem,

138

Di solito lo faccio:

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
Sono d'accordo che questa è la risposta migliore, anche se aggiungerei che la confusione su questo è probabilmente sul passaggio di una funzione come primo argomento $scope.$watche sull'uso di quella funzione per restituire un valore dalla chiusura. Devo ancora imbattermi in un altro esempio di questo, ma funziona ed è il migliore. Il motivo per cui non ho scelto la risposta di seguito (ad es. $scope.$watch('test.name', function (value) {});) È perché mi è stato richiesto di codificare in modo rigido ciò che ho chiamato il mio controller nel mio modello o nel $ stateProvider di ui.router e qualsiasi modifica lì avrebbe inavvertitamente violato l'osservatore.
Morris Singer,

Inoltre, l'unica differenza sostanziale tra questa risposta e la risposta attualmente accettata (che utilizza angular.bind) è se si desidera associare thiso semplicemente aggiungere un altro riferimento thisall'interno della chiusura. Questi sono funzionalmente equivalenti e, nella mia esperienza, questo tipo di scelta è spesso una chiamata soggettiva e la questione di un'opinione molto forte.
Morris Singer,

1
una cosa bella di ES6 sarà l'eliminazione di dover fare le 2 soluzioni sopracitate per ottenere il giusto ambito js . $scope.$watch( ()=> { return this.name' }, function(){} ) Freccia grassa in soccorso
jusopi,

1
puoi anche solo fare() => this.name
coblr

Riesci a farlo funzionare con $scope.$watchCollectione ancora ottenere i oldVal, newValparametri?
Kraken,

23

Puoi usare:

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

Questo funziona JSFiddle con il tuo esempio.


25
Il problema con questo approccio è che JS ora si affida all'HTML, costringendo il controller ad essere associato con lo stesso nome (in questo caso "test") ovunque affinché il $ watch funzioni. Sarebbe molto facile introdurre bug sottili.
jsdw,

Questo risulta funzionare meravigliosamente se stai scrivendo Angular 1 come Angular 2 dove tutto è una direttiva. Object.observe sarebbe fantastico in questo momento però.
Langdon,

13

Simile all'utilizzo del "test" da "TestCtrl come test", come descritto in un'altra risposta, è possibile assegnare "self" al proprio ambito:

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

In questo modo, non sei legato al nome specificato nel DOM ("TestCtrl come test") ed eviti anche la necessità di .bind (this) a una funzione.

... da utilizzare con l'html originale specificato:

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

Voglio solo sapere una cosa, vale a dire, $scopeè un servizio, quindi se aggiungiamo $scope.self = this, quindi in un altro controller se facciamo lo stesso, cosa accadrà lì?
Vivek Kumar il

12

AngularJs 1.5 supporta $ ctrl predefinito per la struttura ControllerAs.

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

Non funziona per me quando utilizzo $ watchGroup, è un limite noto? puoi condividere un link a questa funzione in quanto non riesco a trovare nulla al riguardo?
user1852503

@ user1852503 Vedere docs.angularjs.org/guide/component Tabella comparativa Direttiva / Definizione dei componenti e controllare il record 'controllerAs'.
Niels Steenbeek,

Ora capisco. La tua risposta è un po 'fuorviante. l'identificatore $ ctrl non è correlato al controller come funzione (come ad esempio $ index fa ad esempio in una ripetizione ng), sembra solo essere il nome predefinito per il controller all'interno di un componente (e la domanda non riguarda nemmeno un componente).
user1852503

@ user1852503 1) $ ctrl correla il Controller (Controller as) 2) La domanda riguarda i componenti, poiché menziona: "<div ng-controller =" TestCtrl come test ">". 3) Tutte le risposte in questa pagina sono in qualche modo uguali alla mia risposta. 4) Per quanto riguarda la documentazione $ watchGroup dovrebbe funzionare bene quando si utilizza $ ctrl.name poiché si basa su $ watch.
Niels Steenbeek,

2

puoi effettivamente passare una funzione come primo argomento di un $ watch ():

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

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

Ciò significa che possiamo restituire il nostro riferimento 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);
    });
});

Leggi un post interessante sull'argomento ControllerAs https://toddmotto.com/digging-into-angulars-controller-as-syntax/



0

Scrivere un $ watch nella sintassi ES6 non è stato facile come mi aspettavo. Ecco cosa puoi fare:

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

NOTA : questo non funziona quando View e Controller sono accoppiati in una route o attraverso un oggetto di definizione della direttiva. Ciò che viene mostrato sotto funziona solo quando c'è un "SomeController as SomeCtrl" nell'HTML. Proprio come Mark V. sottolinea nel commento qui sotto, e proprio come dice è meglio fare come Bogdan.

Uso: var vm = this;all'inizio del controller per togliere la parola "questo". Quindi vm.name = 'Max';e nell'orologio io return vm.name. Uso "vm" proprio come @Bogdan usa "self". Questo var, sia esso "vm" o "self" è necessario poiché la parola "this" assume un contesto diverso all'interno della funzione. (quindi restituire this.name non funzionerebbe) E sì, devi raggiungere $ scope nel tuo bellissimo "controller as" come soluzione per raggiungere $ watch. Vedi la Guida allo stile di 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
Funziona finché hai "SomeController as vm" nel tuo HTML. È fuorviante, tuttavia: il "vm.name" nell'espressione watch non ha nulla a che fare con "var vm = this;". L'unico modo sicuro per usare $ watch con "controller as" è passare una funzione come primo argomento, come Bogdan illustra sopra.
Mark Visser,

-1

Ecco come farlo senza $ scope (e $ watch!) I 5 errori più importanti - Abuso dell'orologio

Se stai usando la sintassi "controller as", è meglio e più pulito evitare di usare $ scope.

Ecco il mio codice in JSFiddle . (Sto usando un servizio per contenere il nome, altrimenti il ​​set ES5 Object.defineProperty e i metodi get causano chiamate infinite.

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

Il violino non funziona e questo non osserverà una proprietà dell'oggetto.
Rootical V.

@RooticalV. Il violino sta funzionando. (Assicurati che quando esegui AngualrJS, specifichi il tipo di caricamento come nowrap-head / nowrap-body
Binu Jasim,

scusa ma non sono ancora riuscito a eseguirlo, peccato perché la tua soluzione è molto interessante
happyZZR1400

@happy Assicurati di scegliere la libreria come Angular 1.4. (Non sono sicuro se 2.0 funzionerà) e Carica tipo come Nessun avvolgimento, quindi premi Esegui. Dovrebbe funzionare.
Binu Jasim,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.