Jak stworzyć dyrektywę w AngularJS ? Na przykładzie prostej dyrektywy input-label-text.

Możliwe, że wielu z Was, przeglądając kod, natknęło się na magiczne wyrażenie ‚directive’. Czym tak naprawdę jest? Jak można coś takiego stworzyć samemu? Na te pytania spróbuję odpowiedzieć, opisując krok po kroku tworzenie prostej dyrektywy input-label-text.

  1. Czym jest dyrektywa w AngularJS?

    Jest to reużywalny komponent, w kodzie widoczny najczęściej jako znacznik (tag), atrybut lub wartość atrybutu. Klasycznie składa się z części skryptowej, gdzie możemy zdefiniować potrzebne zmienne, funkcje, etc. oraz z części HTML-owej, czyli tzw. template’a – kawałku kodu, ‚wstrzykiwanego’ w miejsce wywołania dyrektywy.

  2. Jak ją stworzyć? Jak skonfigurować swoje pliki, aby dyrektywa zadziałała?

    Pokaże to na przykładzie komponentu, który łączy ze sobą tagi <input> i <label> oraz implementuje prostą logikę w swoim własnym wydzielonym kontrolerze.

Start – czego potrzebujemy?

Działający moduł angularowy (angular.module) i przynajmniej jedną stronę, plik HTML, który używa naszego modułu. Dodatkowo przyda się zainstalowany Bootstrap, noszący ze sobą spory zastrzyk CSS-owych rozwiązań. Krótko mówiąc, jeśli posiadamy aplikację typu Hello World, jest to wszystko czego potrzebujemy.

Nowy plik JS i zdefiniowanie dyrektywy

Dobrym nawykiem jest stworzenie oddzielnego pliku JS, który zawiera w sobie dyrektywy (lub kilka plików z podobnymi w działaniu dyrektywami). Pamiętajmy oczywiście o włączeniu tego pliku do naszego index.html lub innego pliku konfigurującego (startującego) naszą aplikację.

angular.module('gdzieobejrzeViewApp')//nazwa naszego modulu
  .directive('textFieldControl', [ function () { //nazwa naszej dyrektywy (nieprzypadkowo camelCase)
    return {
      restrict: 'AE', //dyrektywa wywołana jako atrybut lub element (tag)
      require: 'ngModel', //jedynym obowiązkowym atrybutem do zdefiniowania jest ngModel
      scope: {
        isRequired: '=?', //oznacza pole jako wymagany, zoltym backgroundem
        labelText: '@', //nazwa labela - wprowadzany jako tekst (@)
        ngModel: '=?', //zdefiniowanie ngModelu
        isReadonly: '=?', //flaga readonly - tylko do odczytu
        isDisabled: '=?', //flaga disabled - nie mozemy wprowadzac zmian
        type: '@?' //okreslenie typu inputa
      },
      controller: ['$scope', function ($scope) {
        $scope.filteredReadonlyLbl = $scope.ngModel;

        $scope.mouseOutValidation = function () { //wywolywane gdy kursor byl nad inputem, a teraz jest na zewnatrz
          console.log('cursor was over the input, now its outside'); 
        }

        $scope.mouseOverValidation = function () { //kursor nad inputem
          console.log('cursor over the input');
        }

        $scope.focusValidation = function () { //klikniecie w input, przejscie w stan wpisywania tekstu
          console.log('input clicked');
        }
      }],
      link: function (scope, watch) {
      },
      templateUrl: '/views/widget/text-field-control.html' //sciezka do naszego htmla, ktory korzysta z wyzej wymienionyc parametrow i funkcji
    }
  }]);


Tak wygląda zdefiniowanie dyrektywy w naszej aplikacji. Kilka słów wyjaśnień, pomijając komentarze w kodzie.

  • Dyrektywa jak widać jest funkcją – w angularze wszystko jest oparte o funkcje, które coś zwracają. Tak, kontroler również jest funkcją, więc nie jest to nic dziwnego.

  • Pole restrict jest odpowiedzialne za zdefiniowanie w jaki sposób możemy się odnosić do dyrektywy, np. <dyrektywa ng-model=”model”/> jako wywołanie dla restrict E

  • Scope przyjmuje atrybuty, które definiujemy przy wywołaniu dyrektywy – są one używane w kontrolerze i naszym htmlowym templacie. ‚=’ oznacza podanie argumentu jako jakiegos obiektu, ‚@’ natomiast oznacza argument wprowadzany jako zwykły tekst.

  • W naszym przypadku nie potrzebujemy rozbudowanego kontrolera. Przykładem zastosowania jest zdefiniowanie funkcji wykonywanych przez nasz template. W tym przypadku będą to funkcje wypisujące do konsoli dany komunikat, po aktywowaniu ich. Równie dobrze mogłyby zmieniać kolor czcionki lub tła, zmieniać jakieś parametry, itd.

Nowy plik HTML – nasz templateUrl

Teraz stworzymy plik html, który będzie pod „opieką” naszej dyrektywy – np. parametrów ze scope’a czy funkcji naszego kontrolera.

<div class="go-form" ng-class="{'requiredField' : isRequired , 'isReadonly' : isReadonly} ">
  <div class="form-group" >
    <label>{{labelText}}</label>
    <input ng-model="ngModel" name="{{fieldName}}" type="{{type == undefined ? 'text' : type}}" class=" form-control input-sm"
           ng-required="{{isRequired}}" ng-show="!isReadonly"
           popover-trigger="validationEvent"
           ng-mouseout="mouseOutValidation()" ng-mouseover="mouseOverValidation()"
           ng-focus="focusValidation()" ng-readonly="isReadonly" title="{{ngModel}}" ng-disabled="isDisabled">
    <label style="width:178px; font-weight: bold!important; padding-left: 6px; color: rgb(85, 85, 85);" ng-if="isReadonly">{{filteredReadonlyLbl}} </label>
  </div>
</div>

Ze względu na słabość komentarzy w plikach HTML, trochę więcej informacji wypisze tutaj:

  • Nasz cały komponent obudowujemy divem, który na wstępie decyduje jaką klasę CSSową ma przybrać. Robi to oczywiście ze względu na wartość atrybutu naszej dyrektywy – isRequired. Dzięki temu zdefiniować inny wygląd, zachowanie całego diva.

  • Ze scopa dyrektywy, korzystamy tak samo jak ze scopa kontrolera w ‚normalnych’ widokach

  • Jak widać input przyjmuje sporo atrybutów, które możemy customizować właśnie dzięki naszym atrybutom z dyrektywy. To duży plus tego rozwiązania.

  • Funkcje z kontrolera, które zaimplementowaliśmy (te z console.log), możemy teraz wykorzystać do definicji np. ng-focus – dyrektywy, która uruchamia podaną funkcję po kliknięciu na input.

  • Nasza dyrektywa „spina” ze sobą input i label, mając również na uwadze sytuację gdy chcemy wyswietlić komponent w trybie readonly.

Użycie dyrektywy

Dyrektywę możemy teraz użyć w każdym pliku HTML, gwarantując jedynie, podanie na wejściu ngModelu, bez którego komponent nie miałby sensu.

<div text-field-control label-text="Film"
     ng-model="searchFilter.movie" is-required="true"/>

<div text-field-control label-text="Kino"
     ng-model="searchFilter.cinema" is-readonly="true"/>

Przykładowe użycie.

  • Nazwę dyrektywy i atrybutów piszemy oddzielając frazy ‚-‚. Tj. gdy przedtem wszystkie te wyrażenia pisaliśmy camelCasem – teraz słowa oddzielamy ‚-‚. To bardzo ważne, gdyż taka panuje tutaj konwencja.

inputdirective

Tak wygląda nasz komponent użyty 2 razy z różnymi parametrami. Proste?

Nie pozostało Ci nic innego jak spróbować samemu stworzyć swoją własną dyrektywę!

Michał Tomaszewski

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *