Angularjs: «контроллер как синтаксис» и $ watch


153

Как подписаться на изменение свойства при использовании controller asсинтаксиса?

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>  

что насчет этого. $ watch ()? Это действительно так: $. Watch ('name', ...)
— Joao Polo

Ответы:


160

Просто свяжите соответствующий контекст.

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

Пример: http://jsbin.com/yinadoce/1/edit

ОБНОВИТЬ:

Ответ Богдана Герсака фактически эквивалентен, оба ответа пытаются связать thisс правильным контекстом. Однако я нашел его ответ чище.

Сказав это, прежде всего, вы должны понять основную идею, стоящую за этим .

ОБНОВЛЕНИЕ 2:

Для тех, кто использует ES6, с помощью arrow functionвы получите функцию с правильным контекстом OOTB.

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

пример


9
Можем ли мы использовать его без $ scope, чтобы избежать смешивания этого и $ scope?
— Мирон

4
Нет, как я знаю, но это прекрасно. $scopeдля вас это своего рода услуга, которая предоставляет такие методы.
— Рой Мило

Вы можете выяснить , является ли nameв return this.name;ссылается на имя контроллера или свойства « name» здесь?
— Янник Йохем

3
@Jannik, angular.bindвозвращает функцию с ограниченным контекстом (arg # 1). В нашем случае мы привязываем this, который является экземпляром контроллера, к функции (arg # 2), что this.nameозначает свойство nameэкземпляра контроллера.
— Рой Мило

Я думаю, я просто понял, как это работает. Когда вызывается связанная функция, она просто вычисляется до наблюдаемого значения, верно?
— Янник Йохем

138

Я обычно делаю это:

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
Я согласен, что это лучший ответ, хотя я бы добавил, что путаница в этом, вероятно, заключается в передаче функции в качестве первого аргумента $scope.$watchи использовании этой функции для возврата значения из замыкания. Я еще не сталкивался с другим примером этого, но он работает и является лучшим. Причина, по которой я не выбрал ответ ниже (т. $scope.$watch('test.name', function (value) {});Е.), Заключается в том, что он требовал, чтобы я жестко запрограммировал то, что я назвал своим контроллером, в своем шаблоне или в $ stateProvider ui.router, и любое изменение там непреднамеренно сломало бы наблюдателя.
— Моррис Сингер

Кроме того, единственное существенное различие между этим ответом и принятым в настоящее время ответом (который использует angular.bind) заключается в том, хотите ли вы связать thisили просто добавить другую ссылку thisв закрытии. Они функционально эквивалентны, и, по моему опыту, такой выбор часто является субъективным вызовом и вопросом очень сильного мнения.
— Моррис Сингер

1
Хорошая вещь в ES6 - устранение необходимости делать 2 вышеупомянутых обходных пути, чтобы получить правильный объем JS . $scope.$watch( ()=> { return this.name' }, function(){} ) Жирная стрела на помощь
— jusopi

1
Вы также можете просто сделать() => this.name
— coblr

Можете ли вы сделать эту работу с $scope.$watchCollectionи все еще получить oldVal, newValпараметры?
— Кракен

23

Ты можешь использовать:

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

Это работает JSFiddle с вашим примером.


25
Проблема с этим подходом состоит в том, что JS теперь полагается на HTML, заставляя контроллер быть привязанным к одному и тому же имени (в данном случае «test») везде, чтобы $ watch работал. Было бы очень легко вводить тонкие ошибки.
— jsdw

Это прекрасно работает, если вы пишете Angular 1, как Angular 2, где все является директивой. Object.observe был бы потрясающим сейчас, хотя.
— Лэнгдон

13

Аналогично использованию «test» из «TestCtrl в качестве теста», как описано в другом ответе, вы можете назначить «self» вашу область:

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

Таким образом, вы не привязаны к имени, указанному в DOM («TestCtrl as test»), и вы также избегаете необходимости .bind (this) с функцией.

... для использования с указанным оригинальным html:

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

Просто хочу узнать одну вещь, т. Е. $scopeЭто сервис, поэтому, если мы добавим $scope.self = this, то в другой контроллер, если мы сделаем то же самое, что там произойдет?
— Вивек Кумар

12

AngularJs 1.5 поддерживает $ ctrl по умолчанию для структуры ControllerAs.

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

У меня не работает при использовании $ watchGroup, это известное ограничение? Можете ли вы поделиться ссылкой на эту функцию, поскольку я ничего не могу найти об этом.
— user1852503

@ user1852503 См. docs.angularjs.org/guide/component Сравнительная таблица Определение директивы / компонента и проверьте запись 'controllerAs'.
— Нильс Стинбек

Теперь я понимаю. Ваш ответ немного вводит в заблуждение. идентификатор $ ctrl не коррелирует с контроллером как функцией (как $ index делает, например, в ng-repeat), это просто имя по умолчанию для контроллера внутри компонента (и вопрос даже не в составная часть).
— user1852503

@ user1852503 1) $ ctrl соотносит контроллер (Controller as) 2) Вопрос касается компонентов, поскольку в нем упоминается: "<div ng-controller =" TestCtrl as test ">". 3) Все ответы на этой странице как-то совпадают с моим ответом. 4) Что касается документации, $ watchGroup должна просто отлично работать при использовании $ ctrl.name, поскольку она основана на $ watch.
— Нильс Стинбек

2

вы можете передать функцию в качестве первого аргумента $ watch ():

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

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

Это означает, что мы можем вернуть нашу ссылку 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);
    });
});

Прочитайте интересный пост о контроллере как тему https://toddmotto.com/digging-into-angulars-controller-as-syntax/



0

Написание $ watch в синтаксисе ES6 оказалось не так просто, как я ожидал. Вот что вы можете сделать:

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

ПРИМЕЧАНИЕ . Это не работает, когда View и Controller связаны в маршруте или через объект определения директивы. То, что показано ниже, работает только тогда, когда в HTML есть «SomeController as SomeCtrl». Точно так же, как Марк В. указывает в комментарии ниже, так же, как он говорит, что лучше делать, как Богдан.

Я использую: var vm = this;в начале контроллера, чтобы убрать слово «это» с моего пути. Тогда vm.name = 'Max';и в часы я return vm.name. Я использую «vm» так же, как @Bogdan использует «self». Это var, будь то «vm» или «self», необходимо, поскольку слово «this» имеет другой контекст внутри функции. (поэтому возвращение this.name не сработает) И да, вам нужно ввести $ scope в ваше красивое решение "controller as", чтобы достичь $ watch. См. Руководство по стилю Джона Папы: 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
Это работает до тех пор, пока в вашем HTML есть SomeController as vm. Однако это вводит в заблуждение: «vm.name» в выражении наблюдения не имеет ничего общего с «var vm = this;». Единственный безопасный способ использовать $ watch с «controller as» - это передать функцию в качестве первого аргумента, как показывает Богдан выше.
— Марк Виссер

-1

Вот как это сделать без $ рамки (и $ часов!) Топ 5 ошибок - Злоупотребления часов

Если вы используете синтаксис «контроллер как», лучше и чище избегать использования $ scope.

Вот мой код в JSFiddle . (Я использую сервис для хранения имени, в противном случае методы set и get объекта ES5 Object.defineProperty вызывают бесконечные вызовы.

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

Скрипка не работает, и это не будет наблюдать свойство объекта.
— Rootical V.

@RooticalV. Скрипка работает. (Убедитесь, что когда вы работаете с AngualrJS, вы указываете тип загрузки как nowrap-head / nowrap-body
— Binu Jasim

извините, но я все еще не смог запустить его, очень жаль, так как ваше решение очень интересное
— happyZZR1400

@happy Убедитесь, что вы выбрали библиотеку Angular 1.4. (Я не уверен, что 2.0 будет работать) и Загрузить тип как без переноса, и нажмите Выполнить. Он должен работать.
— Бину Джасим
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.