Angularjs: 'Controller als Syntax' und $ watch

153

Wie abonniere ich eine Eigenschaftsänderung, wenn ich die controller asSyntax verwende?

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
quelle
was ist damit. $ watch ()? Es ist gültig: this. $ Watch ('name', ...)
Joao Polo

Antworten:

160

Binden Sie einfach den relevanten Kontext.

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

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

AKTUALISIEREN:

Bogdan Gersaks Antwort ist eigentlich eine Art Äquivalent, beide Antworten versuchen, sich thismit dem richtigen Kontext zu verbinden. Ich fand seine Antwort jedoch sauberer.

Vor diesem Hintergrund muss man in erster Linie die zugrunde liegende Idee verstehen .

UPDATE 2:

Für diejenigen, die ES6 verwenden, erhalten Sie durch die Verwendung arrow functioneine Funktion mit dem richtigen Kontext OOTB.

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

Beispiel

Roy Miloh
quelle
9
Können wir es ohne $ scope verwenden, um eine Mischung aus diesem und $ scope zu vermeiden?
Miron
4
Nein, wie ich weiß, aber es ist vollkommen in Ordnung. $scopeFür Sie ist eine Art Service, der diese Art von Methoden liefert.
Roy Miloh
Können Sie hier klären, ob sich namein return this.name;auf den Namen des Controllers oder die Eigenschaft " name" bezieht ?
Jannik Jochem
3
@Jannik, angular.bindgibt eine Funktion mit einem begrenzten Kontext zurück (Argument 1). In unserem Fall binden wir this, die Instanz des Controllers, an die Funktion (arg # 2), this.namedh die Eigenschaft nameder Instanz des Controllers.
Roy Miloh
Ich glaube, ich habe gerade verstanden, wie das funktioniert. Wenn die gebundene Funktion aufgerufen wird, wird sie einfach auf den beobachteten Wert ausgewertet, oder?
Jannik Jochem
138

Normalerweise mache ich das:

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
quelle
3
Ich stimme zu, dass dies die beste Antwort ist, obwohl ich hinzufügen möchte, dass die Verwirrung darin wahrscheinlich darin besteht, eine Funktion als erstes Argument zu übergeben $scope.$watchund diese Funktion zu verwenden, um einen Wert aus dem Abschluss zurückzugeben. Ich habe noch kein anderes Beispiel dafür gefunden, aber es funktioniert und ist das Beste. Der Grund, warum ich die unten stehende Antwort nicht gewählt habe (dh $scope.$watch('test.name', function (value) {});), ist, dass ich das, was ich meinen Controller nannte, in meiner Vorlage oder im $ stateProvider von ui.router fest codieren muss und jede Änderung dort den Beobachter versehentlich beschädigen würde.
Morris Singer
Der einzige wesentliche Unterschied zwischen dieser Antwort und der derzeit akzeptierten Antwort (die verwendet wird angular.bind) besteht darin, ob Sie an den Abschluss binden thisoder einfach einen anderen Verweis hinzufügen möchten this. Diese sind funktional gleichwertig, und meiner Erfahrung nach ist diese Art der Wahl oft ein subjektiver Aufruf und eine sehr starke Meinung.
Morris Singer
1
Eine schöne Sache an ES6 wird sein, dass Sie die beiden oben genannten Problemumgehungen nicht mehr ausführen müssen , um den richtigen JS- Bereich zu erhalten. $scope.$watch( ()=> { return this.name' }, function(){} ) Fetter Pfeil zur Rettung
Jusopi
1
Sie können auch einfach tun() => this.name
Coblr
Kannst du das $scope.$watchCollectionzum oldVal, newValLaufen bringen und trotzdem die Parameter bekommen?
Kraken
23

Sie können verwenden:

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

Dies funktioniert JSFiddle mit Ihrem Beispiel.

Artyom Pranovich
quelle
25
Das Problem bei diesem Ansatz ist, dass sich der JS jetzt auf den HTML-Code verlässt und den Controller zwingt, überall den gleichen Namen (in diesem Fall "Test") zu verwenden, damit die $ watch funktioniert. Es wäre sehr einfach, subtile Fehler einzuführen.
jsdw
Dies funktioniert wunderbar, wenn Sie Angular 1 wie Angular 2 schreiben, bei dem alles eine Direktive ist. Object.observe wäre jetzt allerdings erstaunlich.
Langdon
13

Ähnlich wie bei der Verwendung des "Tests" von "TestCtrl als Test", wie in einer anderen Antwort beschrieben, können Sie Ihrem Bereich "Selbst" zuweisen:

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

Auf diese Weise sind Sie nicht an den im DOM angegebenen Namen gebunden ("TestCtrl as test") und vermeiden auch die Notwendigkeit, (dies) an eine Funktion zu binden.

... zur Verwendung mit dem angegebenen Original-HTML:

<div ng-controller="TestCtrl as test">
    <input type="text" ng-model="test.name" />
    <a ng-click="test.changeName()" href="#">Change Name</a>
</div>
user4389
quelle
Ich möchte nur eines wissen, dh $scopeeinen Dienst. Wenn wir also hinzufügen $scope.self = this, dann in einem anderen Controller, wenn wir dasselbe tun: Was passiert dort?
Vivek Kumar
12

AngularJs 1.5 unterstützt die Standard $ ctrl für die ControllerAs-Struktur.

$scope.$watch("$ctrl.name", (value) => {
    console.log(value)
});
Niels Steenbeek
quelle
Funktioniert bei mir nicht mit $ watchGroup. Ist dies ein bekanntes Limit? Können Sie einen Link zu dieser Funktion freigeben, da ich nichts darüber finden kann?
user1852503
@ user1852503 Siehe docs.angularjs.org/guide/component Vergleichstabelle Direktive / Komponentendefinition und überprüfen Sie den Datensatz 'controllerAs'.
Niels Steenbeek
Ich verstehe jetzt. Ihre Antwort ist etwas irreführend. Der Bezeichner $ ctrl korreliert nicht mit dem Controller als Feature (wie $ index zum Beispiel bei einer ng-Wiederholung), sondern ist zufällig der Standardname für den Controller innerhalb einer Komponente (und die Frage bezieht sich nicht einmal auf a Komponente).
user1852503
@ user1852503 1) Die $ ctrl korreliert den Controller (Controller as) 2) Die Frage bezieht sich auf Komponenten, da erwähnt wird: "<div ng-controller =" TestCtrl as test ">". 3) Alle Antworten auf dieser Seite stimmen irgendwie mit meiner Antwort überein. 4) In Bezug auf die Dokumentation sollte $ watchGroup bei Verwendung von $ ctrl.name einwandfrei funktionieren, da es auf $ watch basiert.
Niels Steenbeek
2

Sie können tatsächlich eine Funktion als erstes Argument einer $ watch () übergeben:

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

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

Das heißt, wir können unsere this.name-Referenz zurückgeben:

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

Lesen Sie einen interessanten Beitrag zum Thema controllerAs https://toddmotto.com/digging-into-angulars-controller-as-syntax/

Alexandr
quelle
0

Das Schreiben einer $ watch in ES6-Syntax war nicht so einfach wie ich erwartet hatte. Folgendes können Sie tun:

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

HINWEIS : Dies funktioniert nicht, wenn View und Controller in einer Route oder über ein Direktivendefinitionsobjekt gekoppelt sind. Was unten gezeigt wird, funktioniert nur, wenn der HTML-Code einen "SomeController as SomeCtrl" enthält. Genau wie Mark V. im Kommentar unten betont, und genau wie er sagt, ist es besser, so zu tun, wie Bogdan es tut.

Ich benutze: var vm = this;am Anfang des Controllers, um das Wort "dies" aus dem Weg zu räumen. Dann vm.name = 'Max';und in der Uhr ich return vm.name. Ich benutze das "vm" genau wie @Bogdan "self" benutzt. Diese Variable, sei es "vm" oder "self", wird benötigt, da das Wort "this" innerhalb der Funktion einen anderen Kontext annimmt. (Die Rückgabe dieses Namens würde also nicht funktionieren.) Und ja, Sie müssen $ scope in Ihre schöne "Controller as" -Lösung einfügen, um $ watch zu erreichen. Siehe John Papas Style Guide: 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
quelle
11
Dies funktioniert, solange Sie "SomeController as vm" in Ihrem HTML haben. Es ist jedoch irreführend: Der "vm.name" im watch-Ausdruck hat nichts mit "var vm = this;" zu tun. Der einzig sichere Weg, $ watch mit "controller as" zu verwenden, besteht darin, eine Funktion als erstes Argument zu übergeben, wie Bogdan oben veranschaulicht.
Mark Visser
-1

Hier erfahren Sie, wie Sie dies ohne $ scope (und $ watch!) Tun. Top 5 Fehler - Missbrauch der Uhr

Wenn Sie die Syntax "controller as" verwenden, ist es besser und sauberer, die Verwendung von $ scope zu vermeiden.

Hier ist mein Code in JSFiddle . (Ich verwende einen Dienst, um den Namen zu speichern, andernfalls verursachen die Set- und Get-Methoden von ES5 Object.defineProperty unendliche Aufrufe.

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
quelle
Die Geige funktioniert nicht und dies beobachtet keine Objekteigenschaft.
Rootical V.
@RooticalV. Die Geige arbeitet. (Stellen Sie sicher, dass Sie, wenn Sie AngualrJS ausführen, den Lasttyp als Nowrap-Kopf / Nowrap-Körper angeben
Binu Jasim
Entschuldigung, aber ich habe es immer noch nicht geschafft, es auszuführen, so schade, da Ihre Lösung sehr insterersting ist
happyZZR1400
@happy Stellen Sie sicher, dass Sie die Bibliothek als Angular 1.4 auswählen. (Ich bin nicht sicher, ob 2.0 funktioniert) und laden Sie den Typ No Wrap und drücken Sie Run. Es sollte funktionieren.
Binu Jasim