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$elementszolgá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.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.