Ostatnio na blogu przedstawiłem wprowadzenie do biblioteki Polymer. Dziś, tak jak obiecałem w tamtym wpisie, kontynuuję ten temat omawiając data-binding w Polymerze. Myślę, że jest to jedna z bardziej istotnych właściwości tej biblioteki (frameworka?), warto więc co nieco o niej wiedzieć…

Data-binding w Polymerze - rodzaje bindingu

W Polymerze wyróżniamy dwa rodzaje data-bindingu (czyli po naszemu wiązania danych - w dalszej części wpisu będę te nazwy stosować zamiennie): bindowanie jednokierunkowe gdzie przepływ danych odbywa się od komponentu rodzica do dziecka oraz bindowanie dwukierunkowe.

Aby zdefiniować data-binding w Polymerze należy użyć odpowiedniej anotacji. Na przykład:

<custom-element data="[[name]]"></custom-element>

W powyższym przykładzie wiążemy właściwość name komponentu rodzica z właściwością data komponentu (dziecka) custom-element.

Jeśli chodzi o anotacje data-bindingu to wyróżniamy ich dwa rodzaje:

  • [[]] służy do definiowania wiązań jednokierunkowych - tylko zmiana danych w kodzie rodzica wpływa na komponent dziecko (zmiana tych danych w komponencie dziecku nie ma wpływu na rodzica)
  • {{}} definiuje wiązanie automatyczne, które może być bindowaniem zarówno jedno jak i dwukierunkowym (pod warunkiem, że docelowa właściwość komponentu dziecka jest zadeklarowana jako dwukierunkowa)

Data-binding jednokierunkowy

Spójrzmy teraz na przykład bindowania jednokierunkowego:

<dom-module id="child-component">
  <template>
    Hello {{who}}
  </template>

  <script>
    Polymer({
      is: 'child-component',
      properties: {
        who: {
          type: String
        }
      }
    });
  </script>
</dom-module>

<child-component who="[[name]]"></child-component>

Na początek, spójrz na ostatnią linię powyższego przykładu. Przedstawia ona użycie anotacji wiązania jednokierunkowego właściwości name komponentu rodzica. Zakładamy tutaj, że używamy komponentu child-component w jakimś innym komponencie (rodzicu), który ma zdefiniowaną właściwość name. W przedstawionym przykładzie dane zapisane we właściwości name mogą być zmieniane tylko przez komponent rodzica.

Spójrz teraz na skrypt w komponencie child-component. Jak widzisz, zdefiniowałem tam właściwość who typu String. Podczas procesu wiązania danych, wartość właściwości name rodzica zapisywana jest we właściwości who komponentu dziecka. Od tego momentu każda zmiana wartości właściwości name znajdować będzie odzwierciedlenie we właściwości who. Jednocześnie każda zmiana wartości właściwości who w komponencie dziecku, będzie przez komponent rodzica ignorowana - nie będzie miała wpływu na właściwość name.

Tutaj jeszcze mała uwaga co do definicji właściwości who. Jeśli definicja ta ogranicza się tylko do zdefiniowania typu właściwości, można zamiennie użyć zapisu skróconego:

who: String

W powyższym przykładzie znaleźć można jeszcze jedną interesującą rzecz… Jeśli spojrzysz na definicję szablonu child-component zauważysz, że wiążemy go z właściwością who tego komponentu. Oznacza to, że każda zmiana tej właściwości, czy to poprzez zmianę właściwości name rodzica czy wewnętrznie w komponencie, będzie miała odzwierciedlenie w szablonie. Jak widzisz, użyłem tam bindowania automatycznego. Tak na prawdę nie ma znaczenia jakiej anotacji użyłbym w tym przypadku ponieważ, wiązania pomiędzy szablonem widoku a właściwością danego komponentu są zawsze jednokierunkowe!

Bindowanie automatyczne

Jeśli chodzi o data-binding w Polymerze to, jak wspomniałem wcześniej, drugim jego rodzajem jest bindowanie automatyczne. Ten typ wiązania jest w pełni konfigurowalny dzięki czemu możemy za jego pomocą uzyskać bindowanie dwukierunkowe, jak i na przykład, wiązanie jednokierunkowe ale z dziecka do rodzica (czyli w przeciwnym kierunku niż standardowo).

Wiązanie dwukierunkowe

Na początek, spójrz na przykład wiązania dwukierunkowego:

<dom-module id="child-component">
  <template>
    Hello {{who}}
  </template>

  <script>
    Polymer({
      is: 'child-component',
      properties: {
        who: {
          type: String,
          notify: true
        }
      }
    });
  </script>
</dom-module>

<child-component who="{{name}}"></child-component>

Tym razem (spójrz na ostatnią linię) użyłem innej anotacji ({{}}). To raczej oczywiste skoro używamy bindowania automatycznego.

Drugą różnicą w stosunku do poprzedniego przykładu jest deklaracja właściwości who komponentu dziecka. Aby uzyskać wiązanie dwukierunkowe musimy do jego konfiguracji dodać właściwość notify i ustawić ją na true. Robiąc to informujemy Polymera, że chcielibyśmy aby o zmianie wartości właściwości who powiadamiany był komponent rodzic. Dzięki temu uzyskujemy sytuację, że komponent dziecko wie o zmianach w komponencie rodzicu i na odwrót.

Wiązanie jednokierunkowe, odwrotne

Drugi przykład przedstawia użycie bindowania automatycznego do uzyskania odwrotnego wiązania jednokierunkowego. Oznacza to, że każda zmiana wartości bindowanej właściwości komponentu dziecka ma znajdować odzwierciedlenie w komponencie rodzicu. Natomiast każda zmiana wartości właściwości komponentu rodzica ma być ignorowana. Spójrz na kod:

<dom-module id="child-component">
  <template>
    Hello {{who}}
  </template>

  <script>
    Polymer({
      is: 'child-component',
      properties: {
        who: {
          type: String,
          notify: true,
          readOnly: true
        }
      }
    });
  </script>
</dom-module>

<child-component who="{{name}}"></child-component>

Jak pewnie od razu zauważyłeś, dodałem dodatkową właściwość do konfiguracji właściwości who (chodzi o readOnly). Dzięki temu mamy tutaj po prostu klasyczne wiązanie dwukierunkowe z zablokowaną możliwością zmiany właściwości komponentu dziecka. Oczywiście chodzi o możliwość zmiany poprzez binding, z poziomu komponentu rodzica - zmiana właściwości who wewnątrz komponentu child-component jest jak najbardziej możliwa, a do tego będzie miała ona odzwierciedlenie również w komponencie rodzicu.

Wiązanie klasyczne, jednokierunkowe

Kolejny przykład to już raczej ciekawostka. Za pomocą bindowania automatycznego możemy też uzyskać ten sam efekt co w przypadku bindowania jednokierunkowego. Spójrz na poniższy przykład:

<dom-module id="child-component">
  <template>
    Hello {{who}}
  </template>

  <script>
    Polymer({
      is: 'child-component',
      properties: {
        who: {
          type: String
        }
      }
    });
  </script>
</dom-module>

<child-component who="{{name}}"></child-component>

Jak widzisz, w powyższym przykładzie pominąłem użycie ustawień notify oraz readOnly. W ten sposób mam w komponencie dziecku te same ustawienia jak w przypadku pierwszego przykładu z bindowaniem jednokierunkowym. W ostatniej linii jednak użyłem anotacji wiązania automatycznego. Z tego wniosek, że nie ważne jaką anotację użyjesz, aby uzyskać coś więcej niż bindowanie jednokierunkowe, musisz dokładnie wyspecyfikować to w ustawieniach właściwości komponentu dziecka.

Blokowanie wiązania w obie strony

Data-binding w Polymerze potrafi wpędzić nas w pewną pułapkę… Spójrz na poniższy przykład:

<dom-module id="child-component">
  <template>
    Hello {{who}}
  </template>

  <script>
    Polymer({
      is: 'child-component',
      properties: {
        who: {
          type: String,
          notify: true,
          readOnly: true
        }
      }
    });
  </script>
</dom-module>

<child-component who="[[name]]"></child-component>

Jak widzisz, jeśli użyjesz anotacji jednokierunkowej ([[]]) i jednocześnie ustawisz w komponencie dziecku właściwości notify oraz readOnly na true spowodujesz, że powiadamianie o zmianach będzie zablokowane w obie strony. Warto pamiętać, że coś takiego jest możliwe kiedy będziesz się zastanawiać dlaczego coś nie działa…

Bindowanie różnych typów danych

Ok, na temat rodzajów bindingu wiesz już chyba wszystko. Teraz skupimy się na wiązaniu danych do szablonu komponentu.

Wiązanie wartości tekstowej

Najprostszym przykładem takiego bindowania jest wiązanie do wartości tekstowej. W sumie powinieneś już być z tym zaznajomiony, bo binding taki wystąpił w poprzednich przykładach w tym wpisie. Dla porządku spójrz jednak poniżej:

<template>
  Hello {{who}}
</template>

Jak widzisz, aby powiązać właściwość komponentu z szablonem wystarczy użyć anotacji bindingu. Tak jak już wspomniałem, nie ważne, której anotacji użyłeś, wiązanie jest zawsze jednokierunkowe.

Bindowanie do atrybutów HTML

Oprócz powyższego, istnieje również możliwość bindowania do atrybutów elementów HTML:

<template>
  <img src$="https://www.example.com/profiles/{{userID}}.jpg" />
</template>

W powyższym przykładzie, w zależności od wartości właściwości userID wyświetlane jest inne zdjęcie. Zwróć uwagę na sposób przypisania wartości do atrybutu src - użyłem tutaj $= zamiast =. Jest to specjalny sposób na wiązanie właściwości komponentu z atrybutami HTML dostarczany przez Polymera.

Bindowanie wyliczane

Czyli po angielsku “computed bindings”. Pozwalają one na wiązanie szablonu z wynikiem działania funkcji:

<dom-module id="custom-component">
  <template>
    Hello <span>{{computeFullName(first, last)}}</span>
  </template>

  <script>
    Polymer({
      is: 'custom-component',
      properties: {
        first: String,
        last: String
      },
      computeFullName: function(first, last) {
        return first + ' ' + last;
      }
    });
  </script>
</dom-module>

Myślę, że powyższy przykład nie wymaga jakiegoś specjalnego wyjaśnienia - computed bindings to po prostu wywoływanie funkcji w miejscu wiązania.

Bindowanie do tablic

Tablice to przypadek specjalny… Data-binding w Polymerze nie pozwala na wiązanie elementów tablicy na podstawie ich indeksu. Jedyne co można zrobić to bindować się do specyficznych właściwości elementu tablicy (o ile jest on obiektem). Dlatego też, poniższy kod nie jest dozwolony:

<span>{{array[0]}}</span>

Dozwolony jest natomiast poniższy przykład wiązania:

<span>{{array[0].name}}</span>

Oczywiście istnieją bardziej zaawansowane sposoby na poradzenie sobie z tym problemem ale to już jest temat na odrębny wpis…

Data-binding w Polymerze - podsumowanie

Jak widzisz, data-binding w Polymerze nie różni się jakoś szczególnie mocno od tego jak to jest zrobione w innych bibliotekach/frameworkach. Choć muszę przyznać, że Polymer daje pełną kontrolę nad procesem wiązania co na pewno jest jego niewątpliwą zaletą. Wydaje mi się, że znajomość tego jak działa data-binding jest kluczowe do tego aby zacząć sensownie pracować z tą biblioteką… Mam więc nadzieję, że wpis ten okaże się dla Ciebie pomocny.