Angularjs: 'controller as syntax' i $ watch

153

Jak zasubskrybować zmianę właściwości przy użyciu controller asskładni?

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
źródło
co z tym. $ watch ()? To ważne: to. $ Watch ('name', ...)
Joao Polo

Odpowiedzi:

160

Po prostu połącz odpowiedni kontekst.

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

Przykład: http://jsbin.com/yinadoce/1/edit

AKTUALIZACJA:

Odpowiedź Bogdana Gersaka jest właściwie odpowiednikiem, obie odpowiedzi starają się połączyć z odpowiednim thiskontekstem. Jednak jego odpowiedź była dla mnie czystsza.

Mając to na uwadze, przede wszystkim musisz zrozumieć leżącą u podstaw ideę .

AKTUALIZACJA 2:

Dla tych, którzy używają ES6, używając arrow functiondostaniesz funkcję z odpowiednim kontekstem OOTB.

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

Przykład

Roy Miloh
źródło
9
Czy możemy go użyć bez $ scope, aby uniknąć pomieszania this i $ scope?
Miron
4
Nie, jak wiem, ale jest w porządku. $scopedla ciebie jest to rodzaj usługi, która dostarcza tego rodzaju metody.
Roy Miloh,
Czy możesz wyjaśnić, czy namew return this.name;odnosi się tutaj do nazwy administratora lub właściwości „ name”?
Jannik Jochem
3
@Jannik, angular.bindzwraca funkcję z ograniczonym kontekstem (arg # 1). W naszym przypadku łączymy się this, który jest instancją kontrolera, z funkcją (arg # 2), a więc this.nameoznacza własność nameinstancji kontrolera.
Roy Miloh
Myślę, że właśnie zrozumiałem, jak to działa. Kiedy wywoływana jest funkcja związana, po prostu zwraca się do obserwowanej wartości, prawda?
Jannik Jochem
138

Zwykle robię to:

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
źródło
3
Zgadzam się, że to najlepsza odpowiedź, chociaż dodam, że niejasność jest prawdopodobnie związana z przekazaniem funkcji jako pierwszym argumentem $scope.$watchi użyciem tej funkcji do zwrócenia wartości z zamknięcia. Nie spotkałem jeszcze innego przykładu, ale działa i jest najlepszy. Powodem, dla którego nie wybrałem odpowiedzi poniżej (tj. $scope.$watch('test.name', function (value) {});), Jest to, że wymagało to trwałego zakodowania tego, co nazwałem mój kontroler w moim szablonie lub w $ stateProvider ui.router, a każda zmiana spowodowałaby nieumyślne uszkodzenie obserwatora.
Morris Singer
Jedyną istotną różnicą między tą odpowiedzią a obecnie akceptowaną odpowiedzią (która używa angular.bind) jest to, czy chcesz się powiązać, thisczy po prostu dodać kolejne odniesienie do thiszamknięcia. Są one funkcjonalnie równoważne iz mojego doświadczenia wynika, że ​​tego rodzaju wybór jest często subiektywnym wezwaniem i sprawą bardzo mocnej opinii.
Morris Singer
1
jedną fajną rzeczą w ES6 będzie wyeliminowanie konieczności wykonywania dwóch wyżej wymienionych obejść, aby uzyskać właściwy zakres js . $scope.$watch( ()=> { return this.name' }, function(){} ) Gruba strzała na ratunek
jusopi
1
możesz też po prostu zrobić() => this.name
coblr
Czy możesz sprawić, by to działało $scope.$watchCollectioni nadal mieć parametry oldVal, newVal?
Kraken
23

Możesz użyć:

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

To działa JSFiddle z twoim przykładem.

Artem Pranovich
źródło
25
Problem z tym podejściem polega na tym, że JS polega teraz na kodzie HTML, zmuszając kontroler do powiązania go z tą samą nazwą (w tym przypadku „test”) wszędzie, aby $ watch działał. Bardzo łatwo byłoby wprowadzić subtelne błędy.
jsdw
Okazuje się, że działa cudownie, jeśli piszesz Angular 1 jak Angular 2, w którym wszystko jest dyrektywą. Jednak Object.observe byłoby teraz niesamowite.
Langdon
13

Podobnie jak przy użyciu opcji „test” z „TestCtrl jako test”, jak opisano w innej odpowiedzi, możesz przypisać zakres „samodzielnie”:

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

W ten sposób nie jesteś przywiązany do nazwy określonej w DOM („TestCtrl jako test”), a także unikasz konieczności wiązania .bind (this) z funkcją.

... do użytku z określonym oryginalnym kodem 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
źródło
Chcę tylko wiedzieć, że $scopejest to usługa, więc jeśli dodamy $scope.self = this, to w innym kontrolerze, jeśli zrobimy to samo, co się tam stanie?
Vivek Kumar
12

AngularJs 1.5 obsługuje domyślną $ ctrl dla struktury ControllerAs.

$scope.$watch("$ctrl.name", (value) => {
    console.log(value)
});
Niels Steenbeek
źródło
Nie działa dla mnie, gdy używam $ watchGroup, czy to znany limit? czy możesz udostępnić link do tej funkcji, ponieważ nie mogę nic znaleźć na jej temat.
user1852503
@ user1852503 Zobacz docs.angularjs.org/guide/component Tabela porównawcza Dyrektywa / Definicja komponentu i sprawdź rekord „controllerAs”.
Niels Steenbeek
Teraz rozumiem. Twoja odpowiedź jest nieco myląca. identyfikator $ ctrl nie koreluje ze sterownikiem jako funkcją (tak jak $ index robi to na przykład w ng-repeat), po prostu jest to domyślna nazwa kontrolera wewnątrz komponentu (a pytanie nie dotyczy nawet składnik).
user1852503
@ user1852503 1) $ ctrl koreluje kontroler (kontroler jako) 2) Pytanie dotyczy komponentów, ponieważ wspomina: "<div ng-controller =" TestCtrl as test ">". 3) Wszystkie odpowiedzi na tej stronie są w pewnym sensie takie same jak moja odpowiedź. 4) Jeśli chodzi o dokumentację, $ watchGroup powinien działać dobrze, gdy używa się $ ctrl.name, ponieważ jest oparty na $ watch.
Niels Steenbeek
2

faktycznie możesz przekazać funkcję jako pierwszy argument $ watch ():

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

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

Co oznacza, że ​​możemy zwrócić odwołanie 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);
    });
});

Przeczytaj interesujący post o kontrolerze W temacie https://toddmotto.com/digging-into-angulars-controller-as-syntax/

Alexandr
źródło
0

Napisanie zegarka $ watch w składni ES6 nie było tak łatwe, jak się spodziewałem. Oto, co możesz zrobić:

// 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
źródło
-1

UWAGA : To nie działa, gdy widok i kontroler są połączone w trasie lub za pośrednictwem obiektu definicji dyrektywy. To, co pokazano poniżej, działa tylko wtedy, gdy w kodzie HTML znajduje się „SomeController as SomeCtrl”. Tak jak zaznacza Mark V. w komentarzu poniżej, i tak jak mówi, lepiej robić tak, jak robi to Bogdan.

Używam: var vm = this;na początku kontrolera, aby słowo „to” zniknęło mi z drogi. Wtedy vm.name = 'Max';i na zegarku ja return vm.name. Używam „vm” tak samo, jak @Bogdan używa „self”. Ta zmienna, czy to „vm”, czy „self”, jest potrzebna, ponieważ słowo „this” przyjmuje inny kontekst wewnątrz funkcji. (więc zwrócenie this.name nie zadziałałoby) I tak, musisz wstrzyknąć $ scope do swojego pięknego rozwiązania „controller as”, aby dotrzeć do $ watch. Zobacz przewodnik po stylu Johna Papy: 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
źródło
11
Działa to tak długo, jak długo masz w kodzie HTML „SomeController as vm”. Jest to jednak mylące: „vm.name” w wyrażeniu obserwującym nie ma nic wspólnego z „var vm = this;”. Jedynym bezpiecznym sposobem użycia $ watch z "kontrolerem jako" jest przekazanie funkcji jako pierwszego argumentu, jak to ilustruje Bogdan.
Mark Visser
-1

Oto jak to zrobić bez $ scope (i $ watch!) Top 5 błędów - nadużywanie zegarka

Jeśli używasz składni „kontroler jako”, lepiej i czyściej unikać używania $ scope.

Oto mój kod w JSFiddle . (Używam usługi do przechowywania nazwy, w przeciwnym razie metody set i get ES5 Object.defineProperty powodują nieskończone wywołania.

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
źródło
Skrzypce nie działają i nie spowoduje to zaobserwowania właściwości obiektu.
Rootical V.
@RooticalV. Skrzypce działa. (Upewnij się, że gdy używasz AngualrJS należy określić rodzaj obciążenia jako nowrap głowicy / nowrap-ciała
Binu Jasim
przepraszam, ale nadal nie udało mi się go uruchomić, szkoda, ponieważ twoje rozwiązanie jest bardzo insterersting
happyZZR1400
@happy Upewnij się, że wybrałeś bibliotekę jako Angular 1.4. (Nie jestem pewien, czy 2.0 zadziała) i Załaduj jako Bez zawijania i naciśnij Uruchom. Powinno działać.
Binu Jasim,