A premissza
Az AngularJS-ben, amikor definiálunk egy komponenst (vagy egy direktívát), létrehozhatunk belső hatókörű változókat az attribútumokból. Az ehhez szükséges API meglehetősen szövevényes:
bindings: {
attr1: '@',
attr2: '<',
attr3: '=',
attr4: '&'
}
Belefáradtam abba, hogy minden egyes használatkor meg kelljen fizetnem az agyam körbetekerésének árát, ezért ebben a bejegyzésben egyszer s mindenkorra boncolgatjuk a négy szimbólum közötti különbséget.
Közelebbről…
- megtanuljuk, hogyan kell átadni a stringeket (
@
) - megtanuljuk, hogyan kell átadni a dinamikus kifejezéseket (
<
) - megtanuljuk, hogyan kell elkapni a kimenetet (
&
) - megtanuljuk, hogyan kell beállítani a két-kétirányú adatkötéseket (
=
) - megtanuljuk, hogyan csinálhatjuk a fentieket anélkül, hogy a négy közül bármelyiket használnánk
- megtanuljuk, hogy a
<
miért rúgja szét a másik három seggét
Attribútum szövegként való olvasása
Kezdjük a @
-val, a legegyszerűbb a négy közül, mivel egyszerűen szövegként olvassa az attribútumot. Más szóval egy sztringet adunk át a komponensnek.
Tegyük fel, hogy van ez a komponensünk:
app.component("readingstring", {
bindings: { text: '@' },
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});
És így rendereljük:
<readingstring text="hello"></readingstring>
Ezután a következőt kapjuk:
A @
használatával létrehozunk egy belső változót, amelyet a megnevezett attribútum sztring tartalmával töltünk fel. Úgy is mondhatnánk, hogy ez a komponens kezdeti konfigurációjaként szolgál.
Attribútum kiértékelése kifejezésként
Még érdekesebb, hogy egy attribútumot kifejezésként kell kiértékelni, és újra kell értékelni, amikor a kifejezés megváltozik. Dinamikus bemenet!
Meg akarjuk tudni ezt tenni…
<dynamicinput in="outervariable"></dynamicinput>
…és a outervariable
kiértékelését átadni a dynamicinput
-ba.
Az AngularJS 1 előtt.5 előtt az egyetlen szintaxis, ami ehhez rendelkezésünkre állt, a =
:
app.component("dynamicinput",{
bindings: { in: '=' },
template: '<p>dynamic input: <strong>{{$ctrl.in}}</strong></p>'
});
A =
hátránya az volt, hogy kétirányú adatkötést hozott létre, holott csak egyirányúra volt szükségünk. Ez azt is jelentette, hogy a kifejezésnek, amit átadunk, változónak kell lennie.
Az AngularJS 1.5-tel viszont <
kaptunk, ami egyirányú adatkötést jelent. Ez lehetővé teszi számunkra, hogy bármilyen kifejezést használjunk bemenetként, például egy függvényhívást:
<dynamicinput in="calculateSomething()"></dynamicinput>
A komponens implementációja pontosan ugyanez lenne, kivéve, hogy a =
-t kicseréljük <
-re.
Kimenet fogása
Ideje megfordítani a dolgokat – hogyan fogjuk el egy komponens kimenetét? Lásd az alábbi aprócska alkalmazást – a gombok egy gyermekben vannak megjelenítve, és amikor rájuk kattintunk, a külső értéket szeretnénk ennek megfelelően frissíteni.
Ez az a pont, ahol a &
jön a képbe. Az attribútumot utasításként értelmezi, és egy függvénybe csomagolja. A komponens ezután tetszés szerint hívhatja ezt a függvényt, és feltöltheti az utasítás változóit. Kimenet a szülőhöz!
Ha a külső html így néz ki…
Outer value: {{count}}
<output out="count = count + amount"></output>
..akkor a output
megvalósítása a &
használatával így nézhet ki:
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> `
});
Nézzük, hogyan adunk át egy objektumot a feltöltendő változókkal. Ez a tekervényes szintaxis azt jelenti, hogy ahhoz, hogy egy kimenettel rendelkező komponenst használjunk, két dolgot kell tudnunk:
- a használni kívánt attribútum neve(i)
- a varázslatosan létrehozandó változók nevei.
Mert a &
annyira tekervényes, sokan a =
-t használják a kimenetre. A manipulálandó változó átadásával…
Outer value: {{count}}
<output out="count"></output>
…aztán egyszerűen megváltoztatjuk ezt a változót a komponensen belül:
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>`
});
Ez azonban nem igazán szép:
- már megint kétirányú adatkötést végzünk, pedig csak egyirányúra van szükségünk
- lehet, hogy nem akarjuk elmenteni a kimenetet, hanem egyszerűen csak hatni rá
A fenti összes megoldásnál szebb megoldás a <
használata a kimenet létrehozására egy callback átadásával!
Elkészítjük a callback-et a külső vezérlőben…
$scope.callback = function(amount){
$scope.count += amount;
}
…és átadjuk a komponensnek:
<output out="callback"></output>
A komponens most egyszerűen hívja meg ennek megfelelően:
app.component("output",{
bindings: { out: '<' },
template: `
<button ng-click="$ctrl.out(1)">buy one</button>
<button ng-click="$ctrl.out(5)">buy many</button>`
});
Nagyon hasonló a &
-hez, de a bonyolult varázslat nélkül!
Egy érdekes mellékszál, ez a minta pontosan így működik egy komponens kimenete a Reactban.
Kétirányú adatkötés
Ez az a pont, ahol a =
általában AngularJS poszterfiúként tündökölhet. Vegyük ezt az alkalmazást:
Ha így rendereljük…
Outer: <input ng-model="value">
<twoway connection="value"></twoway>
…akkor a twoway
-t a =
segítségével így tudjuk implementálni:
app.component("twowayeq",{
bindings: { connection: '=' },
template: `inner: <input ng-model="$ctrl.connection">`
});
Kétségkívül egyszerű, de jegyezzük meg – meglehetősen ritkán van szükség kétirányú adatkötésre. Gyakran az, amire valójában szükségünk van, az egy bemenet és egy kimenet.
Mivel elérkeztünk ahhoz, hogy hogyan tudjuk megvalósítani a kétirányú kötést csak a <
használatával! Ha ismét létrehozunk egy visszahívási függvényt a külső vezérlőben…
$scope.callback = function(newval){
$scope.value = newval;
}
…és átadjuk az értéket és a visszahívást is…
<twoway value="value" callback="callback"></twoway>
…akkor a komponens így hozható létre:
app.component("twowayin",{
bindings: {
value: '<',
callback: '<'
},
template: `
<input ng-model="$ctrl.value" ng-change="$ctrl.callback($ctrl.value)">
`
});
Kétirányú adatkötést értünk el, de még mindig ragaszkodunk az egyirányú adatáramláshoz. Jobb karma!
A szimbólumok teljes elhagyása
Tény, hogy a négy szimbólum csak rövidítés. Mindent meg tudunk csinálni nélkülük is.
A string passing app…
…amit így rendereltünk…
<readingstring text="hello"></readingstring>
…
…megvalósítható a$element
szolgáltatás elérésével:
app.component("readingstring", {
controller: function($element){
this.text = $element.attr("text");
},
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});
Vagy egy direktívával, a link
-nek átadott attrs
felhasználásával:
app.directive("readingstring", function(){
return {
restrict: 'E',
scope: {},
link: function(scope,elem,attrs){
scope.text = attrs.text;
},
template: '<p>text: <strong>{{text}}</strong></p>'
};
});
A dinamikus beviteli app…
…így jelenik meg…
<dynamicinput in="outervariable"></dynamicinput>
…megvalósítható egy .$watch
hívással a szülői hatókörben:
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>'
});
A kimeneti alkalmazás…
…így jelenik meg…
<output out="count = count + amount"></output>
…megvalósítható lenne a $scope.$apply
meghívásával a szülői hatókörben:
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>`
});
Ez nem egészen ugyanaz, mint a &
, mivel most a szülői hatókört is szennyeztük egy amount
változóval, de azért elég jól mutatja a koncepciót.
Végre a kétirányú alkalmazás…
…renderelve, mint a =
esetében…
<twoway connection="value"></twoway>
…úgy valósítható meg, hogy mind a szülői, mind a gyermek hatókörben $watch
-t állítunk be:
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">`
});
Ez egy kis csalás, mivel most azt feltételezzük, hogy a kötött érték mindig egy karakterlánc, de a lényeg így is megvan!
Összefoglalva
Reméljük, hogy ez az utazás tanulságos volt, és hogy a @
, <
, =
és &
most már kevésbé tűnik ijesztőnek.
És hogy észrevetted, hogy a <
mennyire szétrúgja a többiek seggét! Mindent tud, amit a =
is tud, de a <
sokkal jobban néz ki, ahogy csinálja.
Mindkettő kissé ügyetlen a stringek olvasásában (a <
stringet igényel egy stringben, a =
pedig proxy változót), de ezt elég könnyű vaníliával csinálni, így a @
ne legyen túl pimasz.
Az &
is tud pálcán forogni.