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>
user4389
источник
Просто хочу узнать одну вещь, т. Е. $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/

Александр
источник
1

Вы можете использовать $ onChanges угловой жизненный цикл компонента.

обратитесь к документации здесь: https://docs.angularjs.org/guide/component под компонент на основе применения раздела

Павел Дуров
источник
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);
    });
}
wojjas
источник
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 будет работать) и Загрузить тип как без переноса, и нажмите Выполнить. Он должен работать.
Бину Джасим