AngularJS 1. 5+ Компоненты не поддерживают Watchers, что обходится?
Я обновлял свои пользовательские директивы до новой архитектуры компонентов. Я читал, что компоненты не поддерживают наблюдателей. Это правильно? Если так, как вы обнаруживаете изменения на объекте? Для базового примера у меня есть пользовательский компонент myBox
который имеет дочерний компонент game с привязкой к игре. Если в игровом компоненте есть игра с изменениями, как мне показать предупреждение в myBox? Я так понимаю, есть метод rxJS, возможно ли сделать это чисто по-угловому? Мой JSFiddle
JavaScript
var app = angular.module('myApp', []);
app.controller('mainCtrl', function($scope) {
$scope.name = "Tony Danza";
});
app.component("myBox", {
bindings: {},
controller: function($element) {
var myBox = this;
myBox.game = 'World Of warcraft';
//IF myBox.game changes, show alert message 'NAME CHANGE'
},
controllerAs: 'myBox',
templateUrl: "/template",
transclude: true
})
app.component("game", {
bindings: {game:'='},
controller: function($element) {
var game = this;
},
controllerAs: 'game',
templateUrl: "/template2"
})
HTML
<div ng-app="myApp" ng-controller="mainCtrl">
<script type="text/ng-template" id="/template">
<div style='width:40%;border:2px solid black;background-color:yellow'>
Your Favourite game is: {{myBox.game}}
<game game='myBox.game'></game>
</div>
</script>
<script type="text/ng-template" id="/template2">
<div>
</br>
Change Game
<textarea ng-model='game.game'></textarea>
</div>
</script>
Hi {{name}}
<my-box>
</my-box>
</div><!--end app-->
Ответы
Ответ 1
Написание компонентов без Наблюдатели
В этом ответе описываются пять методов, используемых для написания компонентов AngularJS 1.5 без использования наблюдателей.
Используйте директиву ng-change
какие альт-методы доступны для наблюдения изменений состояния obj без использования часов для подготовки к AngularJs2?
Вы можете использовать директиву ng-change
, чтобы реагировать на изменения ввода.
<textarea ng-model='game.game'
ng-change="game.textChange(game.game)">
</textarea>
А для распространения события на родительский компонент обработчик события должен быть добавлен как атрибут дочернего компонента.
<game game='myBox.game' game-change='myBox.gameChange($value)'></game>
JS
app.component("game", {
bindings: {game:'=',
gameChange: '&'},
controller: function() {
var game = this;
game.textChange = function (value) {
game.gameChange({$value: value});
});
},
controllerAs: 'game',
templateUrl: "/template2"
});
И в родительском компоненте:
myBox.gameChange = function(newValue) {
console.log(newValue);
});
Это предпочтительный метод в будущем. Стратегия использования AngularJS $watch
не является масштабируемой, поскольку она является стратегией опроса. Когда количество слушателей $watch
достигает около 2000, пользовательский интерфейс становится вялым. Стратегия в Angular 2 - сделать структуру более реактивной и не размещать $watch
на $scope
.
Используйте $onChanges
Крюк жизненного цикла
С версией 1.5.3, AngularJS добавил $onChanges
привязку жизненного цикла к службе $compile
.
Из Документов:
Контроллер может предоставить следующие методы, которые действуют как крючки жизненного цикла:
- $onChanges (changesObj) - Вызывается каждый раз, когда обновляются односторонние (
<
) или интерполяционные привязки (@
). changesObj
является хешем, ключи которого являются именами связанных свойств, которые изменились, а значения являются объектом формы { currentValue: ..., previousValue: ... }
. Используйте этот крючок для запуска обновлений внутри компонента, например клонирования связанного значения, чтобы предотвратить случайную мутацию внешнего значения.
- Справочная информация по API-интерфейсу AngularJS - крюки жизненного цикла
Крючок $onChanges
используется для реагирования на внешние изменения в компоненте с <
односторонними связями. Директива ng-change
используется для распространения изменений с контроллера ng-model
вне компонента с привязками &
.
Используйте $doCheck
Крюк жизненного цикла
С версией 1.5.8, AngularJS добавил $doCheck
привязку жизненного цикла к сервису $compile
.
Из Документов:
Контроллер может предоставить следующие методы, которые действуют как крючки жизненного цикла:
-
$doCheck()
- Вызывается каждый оборот цикла дайджеста. Предоставляет возможность обнаруживать изменения и вносить изменения в них. Любые действия, которые вы хотите предпринять в ответ на обнаруженные вами изменения, должны быть вызваны из этого крючка; реализация этого не влияет на то, когда вызывается $onChanges
. Например, этот крючок может быть полезен, если вы хотите выполнить глубокую проверку равенства или проверить объект Date, изменения которого не будут обнаружены с помощью детектора изменений Angular и, следовательно, не запускать $onChanges
. Этот крючок вызывается без аргументов; при обнаружении изменений вы должны сохранить предыдущие значения для сравнения с текущими значениями.
- Справочная информация по API-интерфейсу AngularJS - крюки жизненного цикла
Компонентная связь с require
Директивы могут require контроллеры других директив, чтобы обеспечить связь друг с другом. Это может быть достигнуто в компоненте путем предоставления сопоставления объектов для свойства require. Ключи объекта определяют имена свойств, под которыми требуемые контроллеры (значения объекта) будут привязаны к требуемому компонентовому контроллеру.
app.component('myPane', {
transclude: true,
require: {
tabsCtrl: '^myTabs'
},
bindings: {
title: '@'
},
controller: function() {
this.$onInit = function() {
this.tabsCtrl.addPane(this);
console.log(this);
};
},
templateUrl: 'my-pane.html'
});
Для получения дополнительной информации см. Руководство разработчика AngularJS - Коммуникация между компонентами
Нажмите значения из службы с помощью RxJS
Как насчет ситуации, когда у вас есть Служба, которая удерживает состояние, например. Как я могу нажать на изменения в этой Службе, и другие случайные компоненты на странице должны знать о таком изменении? Борьба с решением этой проблемы в последнее время
Создайте службу с помощью RxJS Extensions для Angular.
<script src="//unpkg.com/angular/angular.js"></script>
<script src="//unpkg.com/rx/dist/rx.all.js"></script>
<script src="//unpkg.com/rx-angular/dist/rx.angular.js"></script>
var app = angular.module('myApp', ['rx']);
app.factory("DataService", function(rx) {
var subject = new rx.Subject();
var data = "Initial";
return {
set: function set(d){
data = d;
subject.onNext(d);
},
get: function get() {
return data;
},
subscribe: function (o) {
return subject.subscribe(o);
}
};
});
Затем просто подпишитесь на изменения.
app.controller('displayCtrl', function(DataService) {
var $ctrl = this;
$ctrl.data = DataService.get();
var subscription = DataService.subscribe(function onNext(d) {
$ctrl.data = d;
});
this.$onDestroy = function() {
subscription.dispose();
};
});
Клиенты могут подписаться на изменения с помощью DataService.subscribe
, и производители могут нажать изменения с помощью DataService.set
.
DEMO на PLNKR.
Ответ 2
$watch
объект доступен внутри объекта $scope
, поэтому вам нужно добавить $scope
внутри вашей функции управления factory, а затем поместить наблюдателя на переменную.
$scope.$watch(function(){
return myBox.game;
}, function(newVal){
alert('Value changed to '+ newVal)
});
Демо здесь
Примечание: Я знаю, что вы преобразовали directive
в component
, чтобы удалить зависимость $scope
, чтобы вы приблизились к шагу ближе к Angular2. Но, похоже, он не удалился по этому делу.Забастовкa >
Обновление
В основном angular 1.5 добавлен метод .component
jus различает две разные функции. Подобно component
.stands, чтобы выполнить конкретное поведение, добавляя selector
, где as directive
означает добавление определенного поведения в DOM. Директива - это всего лишь метод обертки на .directive
DDO (объект определения директивы). Только то, что вы можете видеть, они удалили функцию link/compile
при использовании метода .component
, где у вас была возможность получить angular скомпилированный DOM.
Использовать крючок жизненного цикла $onChanges
/$doCheck
привязки жизненного цикла компонента angular, они будут доступны после версии angular 1.5.3+.
$onChanges (changesObj) - Вызывается, когда привязки обновляются. ИзмененияObj - хэш, ключи которого являются именами связанных свойств.
$doCheck() - вызывается при каждом повороте цикла дайджеста при изменении привязки. Предоставляет возможность обнаруживать и действовать с изменениями.
Используя ту же самую функцию внутри компонента, вы убедитесь, что ваш код будет совместим для перехода на angular 2.
Ответ 3
Для всех, кто интересуется моим решением, я в конечном итоге прибегаю к RXJS Observables, что вам нужно будет использовать, когда вы перейдете к Angular 2. Вот рабочая скрипка для связи между компонентами, это дает мне больше контроля над что посмотреть.
JS FIDDLE RXJS Наблюдения
class BoxCtrl {
constructor(msgService) {
this.msgService = msgService
this.msg = ''
this.subscription = msgService.subscribe((obj) => {
console.log('Subscribed')
this.msg = obj
})
}
unsubscribe() {
console.log('Unsubscribed')
msgService.usubscribe(this.subscription)
}
}
var app = angular
.module('app', ['ngMaterial'])
.controller('MainCtrl', ($scope, msgService) => {
$scope.name = "Observer App Example";
$scope.msg = 'Message';
$scope.broadcast = function() {
msgService.broadcast($scope.msg);
}
})
.component("box", {
bindings: {},
controller: 'BoxCtrl',
template: `Listener: </br>
<strong>{{$ctrl.msg}}</strong></br>
<md-button ng-click='$ctrl.unsubscribe()' class='md-warn'>Unsubscribe A</md-button>`
})
.factory('msgService', ['$http', function($http) {
var subject$ = new Rx.ReplaySubject();
return {
subscribe: function(subscription) {
return subject$.subscribe(subscription);
},
usubscribe: function(subscription) {
subscription.dispose();
},
broadcast: function(msg) {
console.log('success');
subject$.onNext(msg);
}
}
}])
Ответ 4
Небольшой хедз-ап в отношении использования ng-change
, как рекомендовано с принятым ответом, вместе с компонентом angular 1.5.
Если вам нужно посмотреть компонент, который ng-model
и ng-change
не работают, вы можете передать параметры как:
Разметка, в которой используется компонент:
<my-component on-change="$ctrl.doSth()"
field-value="$ctrl.valueToWatch">
</my-component>
Компонент js:
angular
.module('myComponent')
.component('myComponent', {
bindings: {
onChange: '&',
fieldValue: '='
}
});
Разметка компонента:
<select ng-model="$ctrl.fieldValue"
ng-change="$ctrl.onChange()">
</select>
Ответ 5
Доступно в IE11, MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver. Вам нужно ввести $element service в контроллер, который разрывает разделение DOM/контроллера, но я чувствую, что это фундаментальное исключение (т.е. Дефект) в angularjs. Поскольку hide/show является асинхронным, нам нужен обратный вызов on-show, что угловые и angular -bootstrap-tab не предоставляют. Это также требует, чтобы вы знали, какой конкретный элемент DOM вы хотите наблюдать. Я использовал следующий код для контроллера angularjs, чтобы запустить отображение диаграммы Highcharts в режиме on-show.
const myObserver = new MutationObserver(function (mutations) {
const isVisible = $element.is(':visible') // Requires jquery
if (!_.isEqual(isVisible, $element._prevIsVisible)) { // Lodash
if (isVisible) {
$scope.$broadcast('onReflowChart')
}
$element._prevIsVisible = isVisible
}
})
myObserver.observe($element[0], {
attributes: true,
attributeFilter: ['class']
})
Ответ 6
Действительно хороший принятый ответ, но я мог бы добавить, что вы также можете использовать силу событий (немного похоже на сигнал/слоты Qt, если хотите).
Событие транслируется: $rootScope.$broadcast("clickRow", rowId)
любым родительским (или даже $rootScope.$broadcast("clickRow", rowId)
контроллером). Затем в вашем контроллере вы можете обработать событие следующим образом:
$scope.$on("clickRow", function(event, data){
// do a refresh of the view with data == rowId
});
Вы также можете добавить некоторые записи на эту тему (взято отсюда: fooobar.com/questions/2210782/...)
var withLogEvent = true; // set to false to avoid events logs
app.config(function($provide) {
if (withLogEvent)
{
$provide.decorator("$rootScope", function($delegate) {
var Scope = $delegate.constructor;
var origBroadcast = Scope.prototype.$broadcast;
var origEmit = Scope.prototype.$emit;
Scope.prototype.$broadcast = function() {
console.log("$broadcast was called on $scope " + this.$id + " with arguments:",
arguments);
return origBroadcast.apply(this, arguments);
};
Scope.prototype.$emit = function() {
console.log("$emit was called on $scope " + this.$id + " with arguments:",
arguments);
return origEmit.apply(this, arguments);
};
return $delegate;
});
}
});
Ответ 7
Я опоздал. Но это может помочь другим людям.
app.component("headerComponent", {
templateUrl: "templates/header/view.html",
controller: ["$rootScope", function ($rootScope) {
let $ctrl = this;
$rootScope.$watch(() => {
return $ctrl.val;
}, function (newVal, oldVal) {
// do something
});
}]
});