Ich habe meine benutzerdefinierten Anweisungen auf die neue Komponentenarchitektur aktualisiert. Ich habe gelesen, dass Komponenten keine Beobachter unterstützen. Ist das richtig? Wenn ja, wie erkennen Sie Änderungen an einem Objekt? Für ein einfaches Beispiel habe ich eine benutzerdefinierte Komponente myBox
, die ein untergeordnetes Komponentenspiel mit einer Bindung für das Spiel hat. Wenn ein Spiel in der Spielkomponente geändert wird, wie kann ich eine Warnmeldung in der myBox anzeigen? Ich verstehe, dass es eine rxJS-Methode gibt. Ist es möglich, dies rein in eckig zu tun? Mein 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-->
In dieser Antwort werden fünf Techniken beschrieben, die zum Schreiben von AngularJS 1.5-Komponenten ohne die Verwendung von watchers verwendet werden.
ng-change
-Direktive$onChanges
Lebenszyklus-Hook$doCheck
Lebenszyklus-Hookng-change
-Direktivewelche alternativen Methoden stehen zur Verfügung, um obj-Zustandsänderungen ohne Vorbereitung von AngularJs2 zu beobachten?
Sie können die ng-change
-Direktive verwenden, um auf Eingabeänderungen zu reagieren.
<textarea ng-model='game.game'
ng-change="game.textChange(game.game)">
</textarea>
Um das Ereignis an eine übergeordnete Komponente weiterzuleiten, muss der Ereignishandler als Attribut der untergeordneten Komponente hinzugefügt werden.
<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"
});
Und in der übergeordneten Komponente:
myBox.gameChange = function(newValue) {
console.log(newValue);
});
Dies ist die bevorzugte Methode für die Zukunft. Die AngularJS-Strategie zur Verwendung von $watch
ist nicht skalierbar, da es sich um eine Abrufstrategie handelt. Wenn die Anzahl der $watch
-Hörer etwa 2000 erreicht, wird die Benutzeroberfläche schleppend. Die Strategie in Angular 2 besteht darin, das Framework reaktiver zu gestalten und zu vermeiden, $watch
auf $scope
zu platzieren.
$onChanges
Lebenszyklus-HookMit version 1.5.3 fügte AngularJS den $onChanges
Lebenszyklus-Hook zum $compile
-Dienst hinzu.
Aus den Dokumenten:
Der Controller kann die folgenden Methoden bereitstellen, die als Lebenszyklus-Hooks dienen:
- $ onChanges (changesObj) - Wird aufgerufen, wenn Einweg- (
<
) oder Interpolationsbindungen (@
) aktualisiert werden. Die VariablechangesObj
ist ein Hash, dessen Schlüssel die Namen der gebundenen Eigenschaften sind, die sich geändert haben, und die Werte sind ein Objekt der Form{ currentValue: ..., previousValue: ... }
. Verwenden Sie diesen Hook, um Aktualisierungen innerhalb einer Komponente auszulösen, z. B. das Klonen des gebundenen Werts, um eine versehentliche Veränderung des äußeren Werts zu verhindern.- AngularJS Comprehensive Directive API-Referenz - Lebenszyklushaken
Der $onChanges
-Hook wird verwendet, um auf externe Änderungen in der Komponente mit <
-Einwegbindungen zu reagieren. Die ng-change
-Direktive wird verwendet, um Änderungen vom ng-model
-Controller außerhalb der Komponente mit &
-Bindungen zu propagieren.
$doCheck
Lebenszyklus-HookMit Version 1.5.8 fügte AngularJS den $doCheck
Lebenszyklushaken zum $compile
-Dienst hinzu.
Aus den Dokumenten:
Der Controller kann die folgenden Methoden bereitstellen, die als Lebenszyklus-Hooks dienen:
$doCheck()
- Wird in jeder Runde des Digest-Zyklus aufgerufen. Bietet die Möglichkeit, Änderungen zu erkennen und darauf zu reagieren. Alle Aktionen, die Sie als Reaktion auf die erkannten Änderungen durchführen möchten, müssen von diesem Hook aus aufgerufen werden. Die Implementierung dieses Befehls hat keine Auswirkungen auf den Aufruf von$onChanges
. Dieser Hook könnte beispielsweise nützlich sein, wenn Sie eine tiefe Gleichheitsprüfung durchführen oder ein Date-Objekt überprüfen möchten, dessen Änderungen vom Angular-Änderungsdetektor nicht erkannt werden und somit$onChanges
nicht auslösen. Dieser Hook wird ohne Argumente aufgerufen. Wenn Änderungen erkannt werden, müssen Sie die vorherigen Werte zum Vergleich mit den aktuellen Werten speichern.- AngularJS Comprehensive Directive API-Referenz - Lebenszyklushaken
require
Richtlinien können erfordern die Controller anderer Richtlinien, um die Kommunikation untereinander zu ermöglichen. Dies kann in einer Komponente erreicht werden, indem eine Objektzuordnung für die Eigenschaft requir bereitgestellt wird. Die Objektschlüssel geben die Eigenschaftsnamen an, unter denen die erforderlichen Controller (Objektwerte) an den Controller der erforderlichen Komponente gebunden werden.
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'
});
Weitere Informationen finden Sie unter AngularJS-Entwicklerhandbuch - Intercomponent Communication
Wie sieht es aus in einer Situation, in der Sie beispielsweise einen Dienst haben, der den Status "Halten" hat. Wie kann ich Push-Änderungen an diesen Dienst und andere zufällige Komponenten auf der Seite machen, wenn eine solche Änderung bekannt ist? Ich habe in letzter Zeit Schwierigkeiten gehabt, dieses Problem anzugehen
Erstellen Sie einen Dienst mit RxJS Extensions for 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);
}
};
});
Dann abonnieren Sie einfach die Änderungen.
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();
};
});
Kunden können Änderungen mit DataService.subscribe
abonnieren, und Produzenten können Änderungen mit DataService.set
senden.
Die DEMO auf PLNKR .
Das $watch
-Objekt ist innerhalb des $scope
-Objekts verfügbar. Sie müssen also $scope
in der Controller-Werksfunktion hinzufügen und dann den Beobachter über die Variable stellen.
$scope.$watch(function(){
return myBox.game;
}, function(newVal){
alert('Value changed to '+ newVal)
});
Hinweis: Ich weiß, dass Sie
directive
incomponent
konvertiert haben, um die Abhängigkeit von$scope
zu entfernen, sodass Sie .__ einen Schritt näher kommen. Angular2. Aber es scheint, als ob es für diesen Fall nicht entfernt wurde . </ strike>
Update
Grundsätzlich wird bei Winkel 1.5 die .component
-Methode hinzugefügt. Sie unterscheidet zwei verschiedene Funktionen. Wie component
. Führt ein bestimmtes Verhalten das Hinzufügen von selector
aus, wobei directive
ein spezifisches Verhalten zu DOM hinzufügt. Direktive ist nur eine Wrapper-Methode für .directive
DDO (Direktive Definitionsobjekt). Sie können lediglich die link/compile
-Funktion entfernen, während Sie die .component
-Methode verwenden, bei der Sie die Möglichkeit hatten, ein Winkel-kompiliertes DOM zu erhalten.
Verwenden Sie den $onChanges
$doCheck
Lebenszyklus-Hook des Angular-Komponenten-Lebenszyklus-Hooks, der nach der Version Angular 1.5.3+ verfügbar ist.
_/$ onChanges (changesObj) - Wird aufgerufen, wenn Bindungen aktualisiert werden. Die changesObj ist ein Hash, dessen Schlüssel die Namen der gebundenen Eigenschaften sind.
$ doCheck () - Wird bei jeder Änderung des Digest-Zyklus aufgerufen, wenn sich die Bindung ändert. Bietet die Möglichkeit, Änderungen zu erkennen und darauf zu reagieren.
Durch Verwendung derselben Funktion innerhalb der Komponente wird sichergestellt, dass Ihr Code kompatibel ist, um zu Angular 2 zu wechseln.
Für jeden, der an meiner Lösung interessiert ist, greife ich auf RXJS Observables zurück, die Sie verwenden müssen, wenn Sie zu Angular 2 gelangen. Hier ist eine funktionierende Geige für die Kommunikation zwischen Komponenten.
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);
}
}
}])
Ein kleines Heads-up bezüglich der Verwendung von ng-change
, wie von der akzeptierten Antwort empfohlen, zusammen mit einer Winkelkomponente von 1,5.
Falls Sie eine Komponente überwachen müssen, die ng-model
und ng-change
nicht funktioniert, können Sie die Parameter wie folgt übergeben:
Markup in welcher Komponente verwendet wird:
<my-component on-change="$ctrl.doSth()"
field-value="$ctrl.valueToWatch">
</my-component>
Komponente js:
angular
.module('myComponent')
.component('myComponent', {
bindings: {
onChange: '&',
fieldValue: '='
}
});
Komponentenauszeichnung:
<select ng-model="$ctrl.fieldValue"
ng-change="$ctrl.onChange()">
</select>
Ich bin spät dran. Aber es kann einem anderen Volk helfen.
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
});
}]
});
Verfügbar in IE11, MutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver . Sie müssen $ element-Service in den Controller einspeisen, der die Trennung von DOM/Controller halbiert, aber ich glaube, dass dies eine grundlegende Ausnahme (dh ein Fehler) in anglejs ist. Da hide/show async ist, benötigen wir einen On-Show-Callback, der von anglejs und angle-bootstrap-tab nicht bereitgestellt wird. Außerdem müssen Sie wissen, welches spezifische DOM-Element Sie beobachten möchten. Ich habe folgenden Code für den anglejs-Controller verwendet, um die Anzeige des Highcharts-Diagramm-Reflow auszulösen.
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']
})
Wirklich nett akzeptierte Antwort, aber ich kann hinzufügen, dass Sie auch die Kraft von Ereignissen verwenden können (ein bisschen wie in Qt-Signal/Slots, wenn Sie so wollen).
Ein Ereignis wird gesendet: $rootScope.$broadcast("clickRow", rowId)
von einem übergeordneten Controller (oder sogar einem untergeordneten Controller) .__ Dann können Sie das Event in Ihrem Controller wie folgt behandeln:
$scope.$on("clickRow", function(event, data){
// do a refresh of the view with data == rowId
});
Sie können auch einige Protokolle dazu hinzufügen (hier entnommen: https://stackoverflow.com/a/34903433/3147071 )
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;
});
}
});