DyreCzęść NR.6

Witam w kolejnym wpisie kursu Angular JS 1.X. Kurs zbliża się już do końca. Jeszcze dwa wpisy plus ten i wstęp do Angulara uznaję za zakończony.

Jest to ostatni wpis na temat dyrektyw Angular. Jest ich sporo więcej, ale nie omówię ich tutaj wszystkich.

Jedną z omawianych dyrektyw w tym wpisie jest ng-repeat.

Sprawdza się ona znakomicie przy wyświetlaniu kolekcji elementów.

Przejdźmy więc do przykładu. W GitHub API każdy użytkownik posiada link do swojego repozytorium.

GitHubAPI

W przypadku użytkownika Microsoft możemy zobaczyć bogatą kolekcję projektów przetrzymywanych w repozytorium GitHub.

Repozytorium składa się z tablicy obiektów, jest więc to kolekcja.

Wyświetlimy tę kolekcję w tabelce.

GitHubAPI Repos

Przed wyświetleniem, musimy wcześniej oczywiście pobrać JSON reprezentującego tę kolekcję repozytoriów.

(function () {
    var app = angular.module("Kurs", []);

    var MainController = function ($scope, $http) {

        $scope.username = "Microsoft"

        var onRequestCompleted = function (response) {
            $scope.user = response.data;
            
            $http.get($scope.user.repos_url)
              .then(onRepos,onError);
            
        }
        
        var onRepos = function(response) {
          
          $scope.repos = response.data
        }

        var onError = function (reason) {
            $scope.error = "Nie można pobrać informacji"
        }
            
        $scope.search = function(username){
            $http.get("https://api.github.com/users/" +username).
            then(onRequestCompleted, onError);
        }

    }
    


    app.controller("MainController", ["$scope","$http",MainController]);

})();

Jest to bardzo proste. Po pobraniu JSON-a z informacjami o użytkowniku wykonamy kolejne zapytanie GET, tym razem na adres URL podany w obiekcie user.

var onRequestCompleted = function (response) {

   $scope.user = response.data;
            
    $http.get($scope.user.repos_url)
    .then(onRepos,onError);         
}
        
var onRepos = function(response) {  
   $scope.repos = response.data
}

Po pobraniu repozytoriów przypisujemy pobraną informację do$scope.repos.

Teraz, gdy w kontrolerze wszystko jest gotowe przejdźmy do widoku. Czas zobaczyć działanie ng-repeat a w akcji.

Jak on działa?

Działa podobnie jak pętla w foreach w C#. Wewnątrz dyrektywy ng-repate określamy swoją zmienną, która będzie reprezentować jeden element w kolekcji. Później używamy słowa kluczowego in plus nazwę zmiennej przechowującej kolekcję, która powinna znajdować się w $scope.

Ng-repeat będzie powtarzać elementy wewnątrz tagu tr dla każdego elementu w kolekcji. Będziemy mieli więc wiersze w tabeli na wzór pobranej kolekcji repozytoriów.

Każde repozytorium oznaczone zmienną “rep” ma pola takie jak “name” i “language”.

<table>
  <thead>
     <tr>
       <th>Nazwa</th>
       <th>Język</th>
     </tr>
  </thead>
  <tbody>
     <tr ng-repeat="rep in repos">
       <td>{{rep.name}}</td>
       <td>{{rep.language}}</td>
     </tr>
  </tbody>
</table>

Oto cały kod HTML.

<body>
    <div ng-app="Kurs">
      <div ng-controller="MainController">
        <div>
          <p>{{ username }}</p>
          <form name="searchInGitHub">
            <input type="search" placeholder="Nazwa" ng-model="username" />
            <input type="submit" value="Szukaj" ng-click="search(username)" />
          </form>
        </div>
        <div>
          <p>{{error}}</p>
        </div>
        <p>Imie i nazwisko :{{user.name}}</p>
        <p>Email : {{user.email}}</p>
        <p>Adress Bloga : {{user.blog}}</p>
        <p>Utworzony : {{user.created_at}}</p>
        <p>Awatar : </p>
        <p>
          <img width="120" ng-src="{{user.avatar_url}}" title="{{user.name}}" />
        </p>
        
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Language</th>
            </tr>
          </thead>
          <tbody>
            <tr ng-repeat="rep in repos">
              <td>{{rep.name}}</td>
              <td>{{rep.language}}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </body>

Najpierw zostaje pobrany użytkownik GitHub, a później jego repozytoria.

Repozytoria są wyświetlone w postaci tabelki.

ng-repeat

Filtr

Filtry pozwalają na transformacje danych. Przydają się one przy kolekcjach jak i przy pojedynczych polach.

Jak widzimy do tabelki została dodana nowa kolumna “Stars”. Będzie ona przechowywać liczbę gwiazdek danych przez ludzi.

Filtr

Nie podoba mi się jednak formatowanie tej kolumny. Mogę to zmienić korzystając z filtra “number”.

Teraz liczby większe niż 1000 będą odpowiednio formatowane z przecinkiem.

Filtr 2

Stosowanie filtrów jest proste, trzeba pamiętać o znaku “|” a po nim wpisujemy nazwę danego filtru.

<table>
  <thead>
    <tr>
       <th>Name</th>
       <th>Language</th>
       <th>Stars</th>       
    </tr>
   </thead>
   <tbody>
   <tr ng-repeat="rep in repos">
       <td>{{rep.name}}</td>
       <td>{{rep.language}}</td>
        <td>{{rep.stargazers_count | number}}</td>
   </tr>
</tbody>
</table>

Filtrów oczywiście jest dużo więcej. Pozwalają one na przykład - na formatowanie daty do postaci napisu w odpowiedni sposób.

Filtr 3

Mogę też zwiększyć, zmniejszyć wielkość liter.

<tbody>
  <tr ng-repeat="rep in repos | limitTo:5 ">
    <td>{{rep.name | lowercase}}</td>
    <td>{{rep.language |  uppercase }}</td>
    <td>{{rep.stargazers_count | number}}</td>
     <td>{{rep.created_at | date:short}}</td>
  </tr>
</tbody>

Filtrów dla pól jest dużo więcej. Postanowiłem wymienić tylko te.

Filtr 4

Przejedzmy więc do filtrów kolekcji, które można zastosować wewnątrz ng-repeat.

Używając filtru “orderBy” mogę określić sposób wyświetlenia kolejności kolekcji. Pisząc “orderBy: name” sprawiam, że kolekcja repozytoriów będzie sortowana po nazwie od A do Z.

Filtr “filter” filtruje daną kolekcję. Pisząc “filter: {language : ‘C#’” sprawiam, że tylko repozytoria napisane w C# wyświetlą mi się w tabelce.

Filtr “limitTo” określa limit wyświetlenia elementów. Poniżej jest ona równy 15.

<tbody>
  <tr ng-repeat="rep in repos | orderBy:name | filter:{ language : 'C#'}   | limitTo:15">
     <td>{{rep.name | lowercase}}</td>
     <td>{{rep.language |  uppercase }}</td>
     <td>{{rep.stargazers_count | number}}</td>
     <td>{{rep.created_at | date:short}}</td>
  </tr>
</tbody>

Filtr też nie musi być ustalony z góry. Filtr “orderBy” teraz odwołuje się do modelu Angular “sortOrder”.

<tr ng-repeat="rep in repos | orderBy:sortOrder | filter:{ language : 'C#'}   | limitTo:15  ">

Wewnątrz pliku JS mam zdefiniowane, co ta zmienna reprezentuje.

$scope.sortOrder = "name";

Można jednak pójść o krok do przodu. Używając dyrektywy “ng-model”, która dobrze współpracuje z listą rozwijalną.

Wartość ng-model to wybrana wartość w liście rozwijalnej. Znaki “+” i “-“ określają kierunek sortowania.

Minus ustawi listę od największej wartości do najmniejszej. W przypadku liter repozytoria będą wyświetlone od ich nazwy, od litery Z.

<select ng-model="sortOrder">
  <option value="+name">Nazwa Od A</option>
  <option value="-name">Nazwa Od Z</option>
  <option value="stargazers_count">Gwiazdy</option>
</select>

Działa to tak:

Filtr 5

ng-show i ng-hide

Istnieje kolejny, mały problem związany z naszą prostą aplikacją. Otóż zanim pobierzemy dane GitHub, zanim wciśniemy przycisk “szukaj” widzimy nasze niewypełnione pola oraz niewypełnioną tabelkę .

Problem

Z pomocą przychodzą dwie dyrektywy “ng-show” i “ng-hide”, które spełniają ten sam cel.

Są one bardziej wygodne niż polecenia jQuerowe polecenia hide, show. Wynika to z tego, że Angular wydziela logikę widoku od kontrolera.

Jak więc użyć tych dyrektyw?

Wewnątrz dyrektywy ng-show musimy podać wyrażenie logiczne. Wyrażenia logiczne trochę inaczej działają w JavaScript niż np. w C#.

var number = 0;

var t1 = undefined;
var t2 = null;
var t3 = {};
var t4 = "";
var t5 = [];
var t6 = { name: "" };

if (number)
   console.log('number');
if (t1)
   console.log('t1');
if (t2)
   console.log('t2');
if (t3)
   console.log('t3'); //true
if (t4)
   console.log('t4');
if (t5)
   console.log('t5'); //true
if (t6)
   console.log('t6'); //true

Jest to jednak zaleta. Wartość zmiennej “user” w Angularze przed wciśnięciem przycisku i przed pobraniem danych z GitHub jest niezdefiniowana.

Niezdefiniowana wartość w JavaScripcie przekłada się na wartość logiczną “false”.

Czyli jeśli dane zostaną pobrane to

<div ng-show="user">
       <p>Imie i nazwisko :{{user.name}}</p>
       <p>Email : {{user.email}}</p>
       <p>Adress Bloga : {{user.blog}}</p>
       <p>Utworzony : {{user.created_at}}</p>
       <p>Awatar : </p>
       <p>
          <img width="120" ng-src="{{user.avatar_url}}" title="{{user.name}}" />
       </p>
            
       <select ng-model="sortOrder">
          <option value="+name">Nazwa Od A</option>
          <option value="-name">Nazwa Od Z</option>
          <option value="stargazers_count">Gwiazdy</option>
       </select>
            
       <table ng-hide="!repos">
         <thead>
          <tr>
            <th>Nazwa</th>
            <th>Język</th>
            <th>Gwiazdy</th>  
            <th>Utworzone</th>
          </tr>
       </thead>
       <tbody>
        <tr ng-repeat="rep in repos | orderBy:sortOrder | filter:{ language : 'C#'}   | limitTo:15  ">
          <td>{{rep.name | lowercase}}</td>
          <td>{{rep.language |  uppercase }}</td>
          <td>{{rep.stargazers_count | number}}</td>
          <td>{{rep.created_at | date:short}}</td>
        </tr>
      </tbody>
   </table>
</div>

ng-hide działa w podobny sposób, tylko warunek logiczny jest odwrotny. Jeśli wartość logiczna jest prawdziwa ma ona ukryć dany element.

ng-hide ng-show

Teraz elementy będą wyświetlane dopiero po pobraniu.

ng-include

Ostatnia dyrektywa, którą chcę wam pokazać to “ng-include”. Przydaje się ona w wielu scenariuszach.

Pozwala na załączanie kodu html z innego pliku i nie tylko do obecnego widoku.

Jest to użyteczne, gdyż możemy w ten sposób przełamać skompilowaną stronę internetową na mniejsze elementy.

Przydaje się to też, gdy mamy jakiś fragment widoku, który będzie się powtarzać w wielu miejscach na całej stronie internetowej.

Przykładowo może informacje o użytkowniku GitHub miałby się pojawiać w wielu miejscach na stronie.

Co teraz właśnie zrobię. Utworzę nowy plik html w plumber i nazwę go “userView.html”.

userView.html

Do mojego ogólnego widoku dodamy dyrektywę ng-include. Normalnie wewnątrz dyrektyw musi się znajdować odwołanie do pola, ale można też przekazać wartości statycznie jako zwykły napis.

Napis wewnątrz dyrektywy ng-include nakierowuje na plik “userView.html.

<div ng-include="'userView.html'" ng-show="user">
          
</div>

W pliku userView.html znajduje się cała definicja użytkownika GitHub.

<div>
    <p>Imie i nazwisko :{{user.name}}</p>
    <p>Email : {{user.email}}</p>
    <p>Adress Bloga : {{user.blog}}</p>
    <p>Utworzony : {{user.created_at}}</p>
    <p>Awatar : </p>
.........
<table>
.........
</div>

Używanie napisów wewnątrz dyrektyw jest niezalecane. Dlatego teraz ng-include odwołuje się do zmiennej, która będzie zdefiniowana w pliku JS.

<div ng-include="userView" ng-show="user">
          
</div>

Wewnątrz pliku JS deklaruję, co będzie się kryć za tym polem w scopie Angulara.

$scope.userView = 'userView.html'

Dyrektyw jest dużo więcej. Są dyrektywy, które reagują na zdarzenia związane z myszką ng-mouseover, ng-dblclick.

Przykładowo w dyrektywie “ng-mouseover” podajemy funkcję JavaScript, która ma się uruchomić, gdy użytkownik najedzie myszką na dany element.

ng-keypress bazuje na naciśniętych klawiszach w klawiaturze.

ng-class doda klasę CSS jeśli wartość wewnątrz dyrektywy będzie prawdziwa.

Angular pozwala też na napisanie swoich własnych dyrektyw. Zabawa się więc nie kończy na tych zdefiniowanych dyrektywach.