Předpoklad
V AngularJS můžeme při definování komponenty (nebo direktivy) vytvářet proměnné vnitřního oboru z atributů. Rozhraní API, které to umožňuje, je poměrně spletité:
bindings: {
attr1: '@',
attr2: '<',
attr3: '=',
attr4: '&'
}
Už mě nebavilo platit za to, že si při každém použití musím lámat hlavu, a tak si v tomto příspěvku jednou provždy rozpitváme rozdíl mezi těmito čtyřmi symboly.
Konkrétně budeme…
- naučíme se předávat řetězce (
@
) - naučíme se předávat dynamické výrazy (
<
) - naučíme se zachytávat výstup (
&
) - naučíme se nastavovat dvou-.datové vazby (
=
) - naučte se, jak dělat vše výše uvedené, aniž byste použili některý ze čtyř
- naučte se, proč
<
nakopává zadek ostatním třem
Čtení atributu jako textu
Začněme s @
, nejjednodušší z těchto čtyř, protože jednoduše čte atribut jako text. Jinými slovy předáme komponentě řetězec.
Řekněme, že máme tuto komponentu:
app.component("readingstring", {
bindings: { text: '@' },
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});
A vykreslíme ji takto:
<readingstring text="hello"></readingstring>
Takto dostaneme:
Při použití @
se vytvoří vnitřní proměnná naplněná řetězcovým obsahem pojmenovaného atributu. Dalo by se říci, že slouží jako počáteční konfigurace komponenty.
Vyhodnocení atributu jako výrazu
Zajímavější je potřeba vyhodnotit atribut jako výraz a nechat jej přehodnotit při každé změně výrazu. Dynamický vstup!
Chceme, abychom to mohli udělat…
<dynamicinput in="outervariable"></dynamicinput>
…a předat vyhodnocení outervariable
do dynamicinput
.
Předtím AngularJS 1.5 jsme pro to měli pouze syntaxi =
:
app.component("dynamicinput",{
bindings: { in: '=' },
template: '<p>dynamic input: <strong>{{$ctrl.in}}</strong></p>'
});
Nevýhodou =
bylo, že vytvářela obousměrnou datovou vazbu, přestože jsme potřebovali pouze jednosměrnou. To také znamenalo, že výraz, který předáváme, musí být proměnná.
V AngularJS 1.5 jsme však získali <
, což znamená jednosměrnou datovou vazbu. To nám umožňuje použít jako vstup libovolný výraz, například volání funkce:
<dynamicinput in="calculateSomething()"></dynamicinput>
Implementace komponenty by byla úplně stejná, jen bychom vyměnili =
za <
.
Zachytávání výstupu
Čas to otočit – jak zachytíme výstup z komponenty? Podívejte se na malou aplikaci níže – tlačítka jsou vykreslena v podřízené komponentě a po kliknutí na ně chceme odpovídajícím způsobem aktualizovat vnější hodnotu.
Tady přichází na řadu &
. Interpretuje atribut jako příkaz a zabalí jej do funkce. Komponenta pak může tuto funkci volat podle libosti a naplnit proměnné v příkazu. Výstup do nadřazené komponenty!
Pokud naše vnější html vypadá takto…
Outer value: {{count}}
<output out="count = count + amount"></output>
..pak by implementace output
pomocí &
mohla vypadat takto:
app.component("output",{
bindings: { out: '&' },
template: `
<button ng-click="$ctrl.out({amount: 1})">buy one</button>
<button ng-click="$ctrl.out({amount: 5})">buy many</button> `
});
Všimněte si, jak předáváme objekt s proměnnými, které se mají naplnit. Tato spletitá syntaxe znamená, že chceme-li použít komponentu s výstupem, musíme znát dvě věci:
- jména atributů, které chceme použít
- jména proměnných, které se zázračně vytvoří.
Protože je &
tak spletitá, mnozí používají =
k provedení výstupu. Předáním proměnné, se kterou se má manipulovat…
Outer value: {{count}}
<output out="count"></output>
…pak jednoduše změníme tuto proměnnou uvnitř komponenty:
app.component("output",{
bindings: { out: '=' },
template: `<div>
<button ng-click="$ctrl.out = $ctrl.out + 1;">buy one</button>
<button ng-click="$ctrl.out = $ctrl.out + 5;">buy many</button>
</div>`
});
To ale opravdu není moc hezké:
- opět děláme obousměrnou vazbu dat, i když potřebujeme jen jeden způsob
- možná nebudeme chtít výstup uložit, ale prostě na něj působit
Hezčí řešení než všechny výše uvedené je použít <
pro vytvoření výstupu předáním zpětného volání!
Ve vnějším kontroléru vytvoříme zpětné volání…
$scope.callback = function(amount){
$scope.count += amount;
}
…a předáme ho komponentě:
<output out="callback"></output>
Komponenta ho nyní jednoduše odpovídajícím způsobem zavolá:
app.component("output",{
bindings: { out: '<' },
template: `
<button ng-click="$ctrl.out(1)">buy one</button>
<button ng-click="$ctrl.out(5)">buy many</button>`
});
Velmi podobné &
, ale bez složitých kouzel!
Pro zajímavost, přesně podle tohoto vzoru funguje výstup z komponenty v Reactu.
Obousměrná vazba dat
Tady může =
obvykle zazářit jako kluk z plakátu AngularJS. Vezměme si tuto aplikaci:
Pokud ji vykreslíme takto…
Outer: <input ng-model="value">
<twoway connection="value"></twoway>
…pak můžeme implementovat twoway
pomocí =
takto:
app.component("twowayeq",{
bindings: { connection: '=' },
template: `inner: <input ng-model="$ctrl.connection">`
});
Je to sice jednoduché, ale všimněte si – obousměrnou datovou vazbu potřebujeme spíše výjimečně. Často vlastně potřebujete jen vstup a výstup.
Což nás přivádí k tomu, jak můžeme implementovat obousměrnou vazbu pouze pomocí <
! Pokud opět vytvoříme funkci zpětného volání ve vnějším kontroléru…
$scope.callback = function(newval){
$scope.value = newval;
}
…a předáme jak hodnotu, tak zpětné volání…
<twoway value="value" callback="callback"></twoway>
…pak lze komponentu vytvořit takto:
app.component("twowayin",{
bindings: {
value: '<',
callback: '<'
},
template: `
<input ng-model="$ctrl.value" ng-change="$ctrl.callback($ctrl.value)">
`
});
Dosáhli jsme obousměrné vazby dat, ale stále se držíme jednosměrného toku dat. Lepší karma!“
Zcela opustit symboly
Faktem je, že čtyři symboly jsou jen zkratky. Vše, co dělají, můžeme dělat i bez nich.
Aplikace pro předávání řetězců…
…kterou jsme takto vykreslili…
<readingstring text="hello"></readingstring>
…by mohla být realizována přístupem ke službě $element
:
app.component("readingstring", {
controller: function($element){
this.text = $element.attr("text");
},
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});
Nebo pomocí směrnice pomocí attrs
, které jsou předávány do link
:
app.directive("readingstring", function(){
return {
restrict: 'E',
scope: {},
link: function(scope,elem,attrs){
scope.text = attrs.text;
},
template: '<p>text: <strong>{{text}}</strong></p>'
};
});
Dynamický vstup aplikace….
…vykreslená takto…
<dynamicinput in="outervariable"></dynamicinput>
…by mohla být realizována pomocí volání .$watch
v nadřazeném oboru:
app.component("dynamicinput",{
controller: ($scope,$element) => {
let expression = $element.attr("in");
$scope.$parent.$watch(expression, newVal => $scope.in = newVal);
},
template: '<p>dynamic input: <strong>{{in}}</strong></p>'
});
Výstupní aplikace…
…vykreslená takto…
<output out="count = count + amount"></output>
…by mohl být realizován voláním $scope.$apply
v nadřazeném oboru:
app.component("output",{
controller: ($scope,$element,$timeout) => {
let statement = $element.attr("out");
$scope.increaseBy = by => {
$timeout(function(){
$scope.$parent.$apply(`amount = ${by}; ${statement}`);
});
}
},
template: `
<button ng-click="increaseBy(1)">buy one</button>
<button ng-click="increaseBy(5)">buy many</button>`
});
To není úplně totéž jako &
, protože jsme nyní také znečistili nadřazený obor proměnnou amount
, ale přesto to dostatečně dobře ukazuje koncept.
Nakonec obousměrná aplikace…
…vykreslena stejně jako u =
…
<twoway connection="value"></twoway>
…by mohla být realizována nastavením $watch
v rodičovském i podřízeném oboru:
app.component("twoway",{
controller: ($scope,$element,$timeout) => {
let variable = $element.attr("connection");
$scope.$parent.$watch(variable, newVal => $scope.inner = newVal;
$scope.$watch('inner', (newVal='') => $timeout( () => {
$scope.$parent.$apply(`${variable} = "${newVal}";`);
}));
},
template: `inner: <input ng-model="inner">`
});
Je to mírný podvod, protože nyní předpokládáme, že vázaná hodnota je vždy řetězec, ale podstata zůstává!
Zabalení
Doufáme, že tato cesta byla poučná a že @
, <
, =
a &
vám nyní připadají méně zastrašující.
A že jste si všimli, jak <
dává ostatním na zadek! Umí všechno, což umí i =
, ale <
při tom vypadá mnohem lépe.
Oba jsou poněkud nešikovné při čtení řetězců (<
vyžaduje řetězec v řetězci a =
potřebuje zástupnou proměnnou), ale to je dost snadné udělat vanilkou, takže @
by se neměl příliš naparovat.
Taky &
se může jít otáčet na špejli.
Vypadá to, že <
umí všechno.