Repozytorium Web Developera

Angular - podstawy

Przydatne linki

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ą metody controller() obiektu Model aplikacji. Metoda ta rejestruje kontroler jako dostawcę w module, ale nie tworzy instancji kontrolera. Ma to miejsce podczas łączenia dyrektywy ng-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);

      };

  });