Lekce 3 - Blazor - Binding
V předchozí lekci, Blazor - Rozšíření Todo aplikace v .NET Core SPA, jsme v naší Todo aplikaci pokračovali a ukázali si, jak správně reagovat na události.
Dnes si uvedeme, co je to vlastně binding a jaká je výhoda two-way bindingu. Názorně si vše ukážeme na dalším příkladu webové aplikace v Blazor a C# .NET.
Binding
Binding je v podstatě vazba mezi proměnnou v kódu a jejím využitím v HTML části komponenty nebo v podřízené komponentě. Binding může být:
- Jednosměrný - Jednosměrná vazba je užitečná, když potřebujeme reprezentovat data v uživatelském rozhraní pouze pro čtení.
- Obousměrný - Obousměrná vazba se používá, pokud potřebujeme aktualizovat stav komponent při změně hodnoty proměnné. Proto se obvykle používá u vstupních komponent v HTML, kde změna hodnoty uživatelem může přímo ovlivnit chování dalších komponent nebo prvků na stránce.
Příklad
Jak je binding implementován v Blazoru si pro lepší pochopení ukážeme
na jednoduchém příkladu. Budeme převádět uživatelem zadanou hodnotu z
palců na centimetry a obráceně. Vytvořme si novou stránku (tedy komponentu
ve složce Pages/
) s názvem Converter
.
Routa
Protože se jedná o stránku, nastavme ji hned na prvním řádku routu (s
pomocí direktivy @page
) pro definici relativní URL, na které ji
můžeme zobrazit:
@page "/converter"
Odkaz do menu
Abychom nemuseli výše zadanou cestu psát v prohlížeči ručně (i když
si můžete vyzkoušet, že to funguje), přidejme si na ni odkaz do menu. To je
jednoduchá úprava komponenty NavMenu
. "Naklonujme" si blok
<li>...</li>
v seznamu odkazů a upravme cíl
(href
), ikonu (oi-...
) a text odkazu:
<li class="nav-item px-3"> <NavLink class="nav-link" href="converter"> <span class="oi oi-transfer" aria-hidden="true"></span> Převodník </NavLink> </li>
Po spuštění aplikace nám v menu přibude odkaz na naši novou stránku:
Jednosměrný binding
Přidejme si do kódu stránky proměnnou palce
typu
double
a do HTML vstupní pole a text, do kterých bude její
hodnota navázána:
<input value="@palce" type="number" /> <p>Hodnota v palcích je: @palce</p>
@code { double palce = 1; }
Zamysleme se nad tím, jak bude komponenta fungovat. Je zřejmé, že po
spuštění by se ve vstupním poli měla objevit výchozí hodnota proměnné
(v našem případě 1
). Text pod vstupním polem by měl být
"Hodnota v palcích je: 1"
.
Ale co se stane, když ve vstupním poli hodnotu změníme? Pro někoho
možná překvapivě se změní právě jen hodnota ve vstupním poli, ale
nikoliv v textu pod ním. Tento způsob vazby je právě označován jako
jednosměrná vazba. Jazykem programátorů bychom také mohli
říci, že jsme do části HTML předali proměnnou palce
nikoliv referencí, ale hodnotou.
Reakce na událost
onchange
My samozřejmě chceme, aby se při změně hodnoty ve vstupním poli
odpovídajícím způsobem změnil i text pod vstupním polem. Musíme proto k
jednosměrné vazbě přidat ještě reakci na událost onchange
vstupního pole. Do této metody můžeme přidat logiku, která aktualizuje
hodnotu v proměnné palce
:
void UpdateValue(ChangeEventArgs e) { double val = 0; double.TryParse(e.Value.ToString(), out val); palce = val; }
A tuto metodu přiřadíme k události onchange
vstupního
pole:
<input value="@palce" type="number" @onchange="UpdateValue"/>
Můžeme vyzkoušet, že po spuštění aplikace se bude hodnota v textu automaticky aktualizovat podle hodnoty ve vstupním poli.
Obousměrný binding
Jak vidíme, náš příklad je funkční i s jednosměrnou vazbou spolu s
ošetřením události onchange
. Existuje ale jednodušší způsob
a tím je právě obousměrná vazba.
Tuto vazbu vytvoříme s pomocí direktivy @bind
. Zajímavé na
naší předchozí implementaci je, že funguje velice podobně jako interní
implementace @bind
. Ta se také defaultně váže na atribut
value
, používá jako výchozí událost onchange
, po
jejím vyvolání aktualizuje hodnotu v navázané proměnné a při tom ji
automaticky převádí na správný datový typ.
S použitím obousměrné vazby je samozřejmě zápis kratší a přehlednější:
<input @bind="palce" type="number"/> <p>Hodnota v palcích je: @palce</p>
@code { double palce = 1; }
Pole pro centimetry
U obousměrné vazby zůstaneme a rozšíříme komponentu o další vstupní pole a příslušnou proměnnou pro centimetry. Zobrazovaný text můžeme již odstranit:
<label>Palce</label> <input type="number" @bind="palce" /> <span> = </span> <input type="number" @bind="centimetry" /> <label>Centimetry</label>
@code { double palce = 1; double centimetry = 2.54; }
Každý vstup je svázán se svou proměnnou, ale chybí logika, která při změně hodnoty jedné z proměnných přepočítá a upraví tu druhou. K tomu můžeme použít klasické properties ze C# a jejich setter. Úprava pro přepočítání hodnoty v centimetrech po změně hodnoty v palcích by mohla vypadat nějak takto:
double palce = 1; public double Palce { get => palce; set { centimetry = value * 2.54; palce = value; } }
Potom ale nesmíme zapomenout na vstupní pole místo proměnné
palce
navázat vlastnost Palce
:
<input type="number" @bind="Palce" />
Převod z palců na centimetry by nyní měl fungovat. Pokud obdobnou úpravu uděláme i pro centimetry, naše jednoduchá komponenta bude plně funkční.
Další možnosti
Jak jsme zmínili výše, binding se defaultně váže na atribut
value
a používá jako výchozí událost onchange
.
Obě tyto vlastnosti vazby lze jednoduše upravit.
Pro změnu vázaného atributu lze použít místo zkrácené direktivy
@bind="variable"
tvar @bind-propertyName="variable"
.
Například námi použitý zápis @bind="Palce"
by mohl mít také
tvar @bind-value="Palce"
se stejným významem.
Změna výchozí události vazby se provede přidáním dalšího parametru
ve tvaru @bind-propertyName:event="eventName"
. Pokud bychom tedy v
našem případě chtěli aktualizovat hodnotu proměnné hned po stisku
klávesy, nikoliv až po opuštění komponenty, použili bychom delší tvar
direktivy @bind
a připsali bychom další parametr
@bind-value:event="oninput"
.
Vazba na parametr podřízené komponenty
Na samostatném příkladu si můžeme ukázat binding do podřízené komponenty:
<h2>Child Component</h2> <p>Year: @Year</p>
@code { [Parameter] public int Year { get; set; } [Parameter] public EventCallback<int> YearChanged { get; set; } }
EventCallback<>
Pokud chceme použít binding do podřízené komponenty, musí v ní být
kromě příslušné public
property také parametr typu
EventCallback<...>
se stejným datovým typem.
Jmenná konvence nám také důrazně doporučuje pojmenovat tento druhý
parametr stejně jako první parametr s přídavkem Changed
, tak
jak vidíme na předchozím příkladu - vlastnost Year
a
příslušný EventCallback
YearChanged
. Pokud tuto
konvenci dodržíme, nemusíme při použití komponenty druhý parametr vůbec
specifikovat a Blazor si ho přiřadí automaticky:
@page "/ParentComponent" <h1>Parent Component</h1> <p>ParentYear: @ParentYear</p> <ChildComponent @bind-Year="ParentYear" /> <button class="btn btn-primary" @onclick="IncrementYear"> Increment Year </button>
@code { [Parameter] public int ParentYear { get; set; } = 1976; private void IncrementYear() { ParentYear++; } }
Rodičovská komponenta
V rodičovské komponentě máme vlastnost ParentYear
, její
hodnotu navážeme na parametr Year
podřízené komponenty (a
ještě jednou si ji zobrazíme přímo v této komponentě). Po kliknutí na
tlačítko změníme v rodičovské komponentě hodnotu ParentYear
a v prohlížeči uvidíme, že změna nastane jak v přímém zobrazení, tak v
podřízené komponentě.
Změna v rodičovské komponentě
Až potud vše funguje dobře. Problém nastane, pokud bychom uvnitř
podřízené komponenty chtěli hodnotu navázaného parametru Year
měnit. Takzvané zřetězení vazeb bohužel nefunguje, takže pokud bychom i
uvnitř podřízené komponenty použili obousměrný binding na nějaké
vstupní pole <input type="number" @bind="Year" />
, o změně
hodnoty se rodičovská komponenta nedozví. To lze naštěstí obejít
použitím jednosměrné vazby a vyvoláním události podobně jako na
začátku dnešní lekce:
<h2>Child Component</h2> <input type="number" value="@Year" @onchange="OnYearChanged" />
@code { [Parameter] public int Year { get; set; } [Parameter] public EventCallback<int> YearChanged { get; set; } private void OnYearChanged(ChangeEventArgs e) { int year = Year; int.TryParse(e.Value.ToString(), out year); if (year != Year) { Year = year; YearChanged.InvokeAsync(year); } } }
Nyní by měl fungovat binding z rodičovské komponenty do podřízené i obráceně
To je pro tuto lekci vše. Příště se podíváme podrobněji na komponenty. Náměty na další lekce můžete psát do komentářů
V další lekci, Blazor - Komponenty, se podrobněji podíváme na komponenty a možnosti, které nám nabízí parametry.