Angular - podstawy
Przydatne linki
- angular-seed
The seed contains a sample AngularJS application and is preconfigured to install the Angular framework and a bunch of development and testing tools for instant web development gratification.
- Angular Style Guide
A starting point for Angular development teams to provide consistency through good practices. http://johnpapa.net
- 5 AngularJS Antipatterns & Pitfalls
- Angular Official Best Practices
- Advanced Design Patterns and Best Practices
Slideshow from session done @opencodeqc and @MagmaConf, built with AngularJS using best practices expressed in the presentation.
- Angular 1.5 component architecture app
Contacts Manager application built on Angular 1.5 components, ui-router 1.0.0, Firebase. https://courses.toddmotto.com
-
Przykłady
Przykłady
Obiekt $scope
Obiekt ten odpowiada m.in. za transportowanie modelu między widokiem a kontrolerem, nasłuchiwanie czy propagację zmian.
Funkcja nasłuchująca listener function i $watch()
Dzięki live binding i digest cycle każda zmiana zachodząca w kontekście Angulara jest przez niego wyłapywana. Zachodzi więc pytanie: czy każdy element przypisany do $scope
otrzymuje od razu własny obiekt nasłuchujący? Odpowiedź brzmi: nie, gdyż nasłuchiwanie zmian na wszystkich elementach zajęłoby zbyt dużo czasu.
Aby wymusić nasłuchiwanie na elemencie należy skorzystać z interpolacji <span>{{myVar}}</span>
lub dodać ją manualnie przez użycie $watch()
.
Obiekt nasłuchujący explicit watcher $watch()
$watch('watchedElement', function(newValue, oldValue) {
//functions body...
});
Przykład konieczności użycia $watch()
Poniższy kod nie zaktualizuje właściwości product
przy zmianie właściwości factor
przez użytkownika.
HTML:
<div ng-controller="index">
<input type="text" ng-model="factor" /> {{product}}
</div>
Błędny JavaScript:
angular
.module("root", [])
.controller("index", ["$scope", function($scope) {
$scope.factor = 6;
$scope.product = $scope.factor * 2;
}]);
Konieczne jest więc skorzystanie z $watch()
na właściwości factor
.
Poprawny JavaScript:
angular
.module("root", [])
.controller("index", ["$scope", function($scope) {
$scope.factor = 6;
$scope.product = $scope.factor * 2;
$scope.$watch('factor', function() {
$scope.product = $scope.factor * 2;
});
}]);
Metoda $apply()
Wszędzie tam, gdzie AngularJS nie może wykryć zmian samodzielnie, musimy to zrobić ręcznie.
Przykładowo, gdyby w poniższym kodzie nie zastosowano metody $apply
, widok nie zostałby zaktualizowany. Wszystko przez to, że zmiana właściwości msg
odbywa się w zewnętrznej funkcji.
<div ng-controller="defaultCtrl">
<div class="well">Wiadomość: {{msg}}</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5 /angular.min.js">
</script>
<script>
// skrypt z poniższego listingu
</script>
JavaScript:
var app = angular.module('app', []);
app.controller('defaultCtrl', function($scope) {
$scope.go = function() {
setTimeout(function() {
$scope.msg = 'Wow, jestem opóźnioną informacją!';
console.log('message:' + $scope.msg);
$scope.$apply();
}, 2000);
}
$scope.go();
});
Metoda $digest()
Metoda wywołuje metodę $watch()
w $scope
i jego potomnych. Nigdy nie należy z niej korzystać, jest wywoływana przez metodę $apply()
(jedyny przypadek możliwy do użycia to unit testy).
Kontrolery
Kontrolery to porcje kodu, których celem jest zapewnienie logiki biznesowej przez rozszerzanie zasięgu. Tworzone są one za pomocą metodycontroller()
obiektuModel
aplikacji. Metoda ta rejestruje kontroler jako dostawcę w module, ale nie tworzy instancji kontrolera. Ma to miejsce podczas łączenia dyrektywyng-controller
w szablonie komponentu AngularJS. W przypadku tworzenia nowej instancji kontrolera w środowisku AngularJS tworzony jest również nowy zasięg podrzędny powiązany z tym kontrolerem.
Na podstawie podrozdziału Relacja między zasięgami i kontrolerami strona 456 z 22 rozdziału książki Node.js, MongoDB, AngularJS - Kompendium wiedzy. Brad Dayley
Wstrzykiwanie zależności
Obiekt Value
Jest to zwykły obiekt, który możemy wstrzykiwać.
var myMod = angular.module('myMod', []);
myMod.value('modMsg', 'Powitanie z mojego modułu');
myMod.controller('controllerB', ['$scope', 'modMsg', function($scope, msg) {
$scope.message = msg;
}]);
/////
var myApp = angular.module('myApp', ['myMod']);
myApp.value('appMsg', 'Powitanie z mojej aplikacji');
myApp.controller('controllerA', ['$scope', 'appMsg', function($scope, msg) {
$scope.message = msg;
}]);
/////
console.log(myApp.requires);
Do myApp
wstrzyknięto cały moduł myMod
, więc myApp
ma dostęp nie tylko do controllerA
, ale również do controllerB
. Poniższe użycie zwraca to, czego się spodziewamy:
<!doctype html>
<html ng-app="myApp">
<head>
<title>”Wstrzykiwanie” zależności w środowisku AngularJS</title>
</head>
<body>
<div ng-controller="controllerA">
<h2>Komunikat aplikacji:</h2>
{{message}}
</div><hr>
<div ng-controller="controllerB">
<h2>Komunikat modułu:</h2>
{{message}}
</div>
<script src="http://code.angularjs.org/1.2.9/angular.min.js"></script>
<script src="/js/injector.js"></script>
</body>
</html>
Na podstawie podrozdziału Implementowanie wstrzykiwania zależności strona 452 z 21 rozdziału książki Node.js, MongoDB, AngularJS - Kompendium wiedzy. Brad Dayley
Metoda factory
Jeśli serwis, kontroler itd. potrzebują obiektu wstrzykiwanego z fabryki, tworzy go ona na żądanie. Raz stworzony obiekt jest dostępny dla wszystkich serwisów, które potrzebują go wstrzyknąć. Factory
różni się od Value
tym, że może użyć funkcji do stworzenia obiektu, który zwraca.
<body>
<div ng-controller="defaultCtrl">
<pre>
$scope.oneFactory = {{oneFactory}};
</pre>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js">
</script>
<script>
// skrypt z poniższego listingu
</script>
</body>
Listing JavaScript:
var app = angular.module('app', []);
app.value("stringValue", "AngularJS");
app.factory("oneFactory", ['stringValue', function(stringValue) {
return "Wartość z fabryki + wartość z value: " + stringValue;
}]);
app.controller('defaultCtrl', ['$scope', 'oneFactory',
function($scope, oneFactory) {
// dostęp na poziomie kontrolera
console.log(oneFactory);
// przypisujemy do scope, by uzyskać widoczność w widoku
$scope.oneFactory = oneFactory;
}
]);
Metoda service
<body>
<div ng-controller="defaultCtrl">
<pre>
$scope.newValue = {{newValue}};
$scope.newValue2 = {{newValue2}};
</pre>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js">
</script>
<script>
// skrypt z poniższego listingu
</script>
</body>
Listing JavaScript:
var app = angular.module('app', []);
app.value("stringValue", "AngularJS");
function OneService() {
this.printLog = function() {
console.log("Log z serwisu - AngularJS");
},
this.newValue = function() {
return "Nowa wartość z serwisu!";
}
};
app.service("oneService", OneService);
app.service("twoService", function() {
this.printLog = function() {
console.log("Log z drugiego serwisu - AngularJS");
},
this.newValue = function() {
return "Nowa wartość z drugiego serwisu!";
}
});
app.controller('defaultCtrl', function($scope, oneService, twoService) {
oneService.printLog();
$scope.newValue = oneService.newValue();
twoService.printLog();
$scope.newValue2 = twoService.newValue();
});
Emitowanie i rozgłaszanie zdarzeń
Zdarzenia pozwalają wysyłać powiadomienia o wystąpieniu zdarzenia do różnych poziomów w zasięgu.
Na podstawie podrozdziału Emitowanie i rozgłaszanie zdarzeń strona 463 z 22 rozdziału książki Node.js, MongoDB, AngularJS - Kompendium wiedzy. Brad Dayley
Metoda $emit()
Metoda ta pozwala na wysłanie zdarzenia w górę hierarchii zasięgu nadrzędnego (Character
do Characters
).
Metoda $broadcast()
Metoda ta pozwala na wysłanie zdarzenia w dół hierarchii zasięgu nadrzędnego (Characters
do Character
).
Metoda $on()
Metoda używana do obsługi zdarzenia emitowanego lub rozgłaszanego. scope.$on(nazwa, element_nasłuchujący)
, gdzie element_nasłuchujący
to funkcja akceptująca zdarzenie jako pierwszy parametr oraz dowolne argumenty przekazane przez metodę $emit()
lub $broadcast()
jako kolejne parametry.
Przykład
<!doctype html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>Zdarzenia zasięgów AngularJS</title>
<style>
div{padding:5px; font: 18px bold;}
span{padding:3px; margin:12px; border:5px ridge;
cursor:pointer;}
label{padding:2px; margin:5px; font: 15px bold;}
p{padding-left:22px; margin:5px; }
</style>
</head>
<body>
<div ng-controller="Characters">
<span ng-repeat="name in names"
ng-click="changeName()">{{name}}</span>
<div ng-controller="Character"><hr>
<label>Imię: </label><p>{{currentName}}</p>
<label>Rasa: </label><p>{{currentInfo.race}}</p>
<label>Broń: </label><p>{{currentInfo.weapon}}</p>
<span ng-click="deleteChar()">Usuń</span>
</div>
</div>
<script src="http://code.angularjs.org/1.2.9/angular.min.js"></script>
<script src="js/scope_events.js"></script>
</body>
</html>
Kontroler Characters
na wywołanie funkcji changeName()
rozsyła dzieciom zdarzenie CharacterChanged
. Funkcja ta wywoływana jest na kliknięcie elementu span
z nazwą postaci {{name}}
. Kontroler ten również na odbiór zdarzenia CharacterDeleted
wywołuje swój callback, co kończy się aktualizacją kontrolera Character
za pomocą zdarzenia CharacterChanged
.
Kontroler Character
na zajście zdarzenia CharacterChanged
aktualizuje swoje dane. Ponadto na kliknięcie elementu span
wywołującego funkcję deleteChar()
rozsyła zdarzenie CharacterDeleted
w górę hierarchii zasięgu, przez co możliwe jest odebranie zdarzenia przez kontroler Characters
.
angular
.module('myApp', [])
.controller('Characters', function($scope) {
$scope.names = ['Frodo', 'Aragorn', 'Legolas', 'Gimli'];
$scope.currentName = $scope.names[0];
$scope.changeName = function() {
$scope.currentName = this.name;
$scope.$broadcast('CharacterChanged', this.name);
};
$scope.$on('CharacterDeleted', function(event, removeName) {
var i = $scope.names.indexOf(removeName);
$scope.names.splice(i, 1);
$scope.currentName = $scope.names[0];
$scope.$broadcast('CharacterChanged', $scope.currentName);
});
})
.controller('Character', function($scope) {
$scope.info = {'Frodo':{weapon:'Żądło', race:'Hobbit'},
'Aragorn':{weapon:'Miecz', race:'Człowiek'},
'Legolas':{weapon:'Łuk', race:'Elf'},
'Gimli':{weapon:'Topór', race:'Krasnolud'}};
$scope.currentInfo = $scope.info['Frodo'];
$scope.$on('CharacterChanged', function(event, newCharacter) {
$scope.currentInfo = $scope.info[newCharacter];
});
$scope.deleteChar = function() {
delete $scope.info[$scope.currentName];
$scope.$emit('CharacterDeleted', $scope.currentName);
};
});