Premissi
AngularJS:ssä, kun määrittelemme komponentin (tai direktiivin), voimme luoda attribuuteista sisäisen alueen muuttujia. API tätä varten on melko mutkikas:
bindings: {
attr1: '@',
attr2: '<',
attr3: '=',
attr4: '&'
}
Väsyin maksamaan hinnan aivojeni kietomisesta sen ympärille joka käyttökerralla, joten tässä postauksessa puramme neljän symbolin välisen eron lopullisesti.
Kohtaisesti…
- oppia siirtämään merkkijonoja (
@
) - oppia siirtämään dynaamisia lausekkeita (
<
) - oppia pyydystämään ulostuloja (
&
) - oppia asentamaan kahden-way data bindings (
=
) - oppia, miten tehdä kaikki edellä mainitut asiat käyttämättä mitään näistä neljästä
- oppia, miksi
<
päihittää muut kolme
Attribuutin lukeminen tekstinä
Aloitetaan @
:sta, joka on näistä neljästä suoraviivaisin, koska se yksinkertaisesti lukee attribuutin tekstinä. Toisin sanoen annamme komponentille merkkijonon.
Esimerkiksi meillä on tämä komponentti:
app.component("readingstring", {
bindings: { text: '@' },
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});
Ja renderöimme sen näin:
<readingstring text="hello"></readingstring>
Tällöin saamme seuraavanlaisen tuloksen:
Käyttämällä @
luodaan sisäinen muuttuja, joka on täytetty nimetyn attribuutin merkkijonosisällöllä. Voisi sanoa, että se toimii komponentin alustavana konfiguraattorina.
Aattribuutin evaluointi lausekkeena
Kiinnostavampaa on tarve evaluoida attribuutti lausekkeena ja saada se evaluoitua uudelleen aina kun lauseke muuttuu. Dynaaminen syöttö!
Haluamme pystyä tekemään tämän…
<dynamicinput in="outervariable"></dynamicinput>
…ja siirtää outervariable
:n evaluoinnin dynamicinput
:een.
Ensimmäisenä AngularJS 1.5, ainoa syntaksi tähän oli =
:
app.component("dynamicinput",{
bindings: { in: '=' },
template: '<p>dynamic input: <strong>{{$ctrl.in}}</strong></p>'
});
=
:
app.component("dynamicinput",{
bindings: { in: '=' },
template: '<p>dynamic input: <strong>{{$ctrl.in}}</strong></p>'
});
Haittapuolena =
oli se, että se loi kaksisuuntaisen datan sidonnan, vaikka tarvitsimme vain yksisuuntaista. Tämä tarkoitti myös sitä, että välittämämme lausekkeen on oltava muuttuja.
Mutta AngularJS 1.5:n myötä saimme <
, joka tarkoittaa yksisuuntaista datan sitomista. Tämä mahdollistaa sen, että voimme käyttää mitä tahansa lauseketta syötteenä, kuten funktiokutsua:
<dynamicinput in="calculateSomething()"></dynamicinput>
Komponentin toteutus olisi täsmälleen sama, paitsi että vaihtaisimme =
:n <
:n tilalle.
Lähdön sieppaaminen
Aika kääntää asiat toisinpäin – miten sieppaamme komponentin lähdön? Katso alla olevaa pientä sovellusta – painikkeet renderöidään lapsessa, ja kun niitä napsautetaan, haluamme päivittää ulomman arvon vastaavasti.
Tässä kohtaa &
tulee mukaan. Se tulkitsee attribuutin lausekkeeksi ja kietoo sen funktioon. Komponentti voi sitten kutsua tätä funktiota mielensä mukaan ja täyttää lausekkeen muuttujat. Output to the parent!
Jos ulkoinen html näyttää tältä…
Outer value: {{count}}
<output out="count = count + amount"></output>
…niin output
:n toteutus &
:n avulla voisi näyttää tältä:
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> `
});
Huomaa, kuinka välitämme objektin, jossa on täytettävät muuttujat. Tämä mutkikas syntaksi tarkoittaa sitä, että voidaksemme käyttää komponenttia, jossa on ulostulo, meidän on tiedettävä kaksi asiaa:
- käytettävän attribuutin nimi (attribuuttien nimet)
- maagisesti luotavien muuttujien nimet.
Koska &
on niin mutkikas, moni käyttää ulostulon tekemiseen =
. Välittämällä käsiteltävä muuttuja…
Outer value: {{count}}
<output out="count"></output>
…muutamme sitten yksinkertaisesti tuota muuttujaa komponentin sisällä:
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>`
});
Tämä ei tosin ole kovin kaunista:
- tehdään taas kaksisuuntaista datan sitomista, vaikka tarvitaan vain yksi tapa
- ei ehkä haluta tallentaa tulostetta, vaan vain toimia sen mukaan
Kaikkea yllämainittua mukavampi ratkaisu on käyttää <
:tä tulosteen luomiseksi välittämällä callback!
Luomme callbackin ulommassa kontrollerissa…
$scope.callback = function(amount){
$scope.count += amount;
}
…ja välitämme sen komponentille:
<output out="callback"></output>
Komponentti nyt yksinkertaisesti kutsuu sitä vastaavasti:
app.component("output",{
bindings: { out: '<' },
template: `
<button ng-click="$ctrl.out(1)">buy one</button>
<button ng-click="$ctrl.out(5)">buy many</button>`
});
Hyvin samankaltainen kuin &
, mutta ilman monimutkaista taikaa!
Kiinnostavana sivuhuomautuksena, tämä kuvio on juuri se, miten komponentin ulostulo toimii Reactissa.
Kaksisuuntainen datan sitominen
Tässä kohtaa =
saa yleensä loistaa AngularJS:n julistepoikana. Otetaan tämä sovellus:
Jos renderöimme sen näin…
Outer: <input ng-model="value">
<twoway connection="value"></twoway>
…niin voimme toteuttaa twoway
käyttäen =
näin:
app.component("twowayeq",{
bindings: { connection: '=' },
template: `inner: <input ng-model="$ctrl.connection">`
});
Todennäköisesti helppoa, mutta huomaa – on melko harvinaista tarvita kaksisuuntaista datan sidontaa. Usein se, mitä itse asiassa halutaan, on tulo ja lähtö.
Miten pääsemme siihen, miten voimme toteuttaa kaksisuuntaisen sidonnan käyttämällä vain <
! Jos taas luomme ulompaan ohjaimeen callback-funktion…
$scope.callback = function(newval){
$scope.value = newval;
}
…ja välitämme sekä arvon että callbackin…
<twoway value="value" callback="callback"></twoway>
…niin komponentti voidaan luoda näin:
app.component("twowayin",{
bindings: {
value: '<',
callback: '<'
},
template: `
<input ng-model="$ctrl.value" ng-change="$ctrl.callback($ctrl.value)">
`
});
Olemme saavuttaneet kaksisuuntaisen datan sitomisen, mutta noudatamme silti yksisuuntaista datavirtaa. Parempi karma!
Jättämällä symbolit kokonaan pois
Tosiasiassa neljä symbolia ovat vain oikoteitä. Voimme tehdä kaiken, mitä ne tekevät, myös ilman niitä.
The string passing app…
…that we rendered like this…
<readingstring text="hello"></readingstring>
….voitaisiin toteuttaa käyttämällä $element
-palvelua:
app.component("readingstring", {
controller: function($element){
this.text = $element.attr("text");
},
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});
Vai direktiivin avulla käyttämällä attrs
, jotka välitetään 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>'
};
});
Dynaamisen syötteen sovellus…
…renderöitynä näin…
<dynamicinput in="outervariable"></dynamicinput>
…voitaisiin toteuttaa käyttämällä .$watch
-kutsua vanhemmassa laajuudessa:
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>'
});
Tulostussovellus…
…renderöitynä näin…
<output out="count = count + amount"></output>
…voitaisiin toteuttaa kutsumalla $scope.$apply
vanhemmassa scope:ssa:
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>`
});
Tämä ei ole aivan sama asia kuin &
, koska olemme nyt myös saastuttaneet vanhemman scope:n amount
-muuttujalla, mutta silti se osoittaa konseptin riittävän hyvin.
Viimein kaksisuuntainen sovellus…
…renderöityy kuten =
…
<twoway connection="value"></twoway>
…voitaisiin toteuttaa asettamalla $watch
sekä vanhempaan että lapseen:
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">`
});
Tämä on pientä huijausta, koska oletamme nyt, että sidottu arvo on aina merkkijono, mutta ydin on silti olemassa!
Pakkaaminen
Toivomme, että tämä matka on ollut opettavainen ja että @
, <
, =
ja &
tuntuvat nyt vähemmän pelottavilta.
Ja että huomasit, kuinka <
pieksee muut! Se osaa kaiken, minkä myös =
osaa, mutta <
näyttää paljon paremmalta tehdessään sen.
Kumpikin on hieman kömpelö merkkijonojen lukemisessa (<
vaatii merkkijonon merkkijonossa, ja =
tarvitsee proxy-muuttujan), mutta se on tarpeeksi helppo tehdä vaniljalla, joten @
:n ei kannattaisi ryhtyä liiaksi ylimieliseksi.
Sekä &
voi mennä pyörimään kepin varassa.