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.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.