De vooronderstelling

In AngularJS, wanneer we een component (of een directive) definiëren, kunnen we inner scope variabelen maken van attributen. De API om dit te doen is nogal ingewikkeld:

bindings: {
attr1: '@',
attr2: '<',
attr3: '=',
attr4: '&'
}

Ik werd moe van het betalen van de prijs van het wikkelen van mijn hersenen rond het elke keer dat ik gebruikt, dus in deze post zullen we ontleden het verschil tussen de vier symbolen eens en voor altijd.

Specifiek, zullen we…

  • leren hoe je strings doorgeeft (@)
  • leren hoe je dynamische expressies doorgeeft (<)
  • leren hoe je output opvangt (&)
  • leren hoe je twee-manier data bindings op te zetten (=)
  • leren hoe je al het bovenstaande kunt doen zonder een van de vier te gebruiken
  • leren waarom < de andere drie verslaat

Een attribuut lezen als tekst

Laten we beginnen met @, de meest eenvoudige van de vier, omdat het eenvoudigweg het attribuut als tekst leest. Met andere woorden, we geven een string door aan de component.

Stel dat we deze component hebben:

app.component("readingstring", {
bindings: { text: '@' },
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});

En we renderen het als volgt:

<readingstring text="hello"></readingstring>

Dit is wat we krijgen:

Het gebruik van @ creëert een innerlijke variabele die wordt gevuld met de string-inhoud van het genoemde attribuut. Je zou kunnen zeggen dat het dient als een initiële configuratie van de component.

Evalueren van een attribuut als een expressie

Interessanter is de noodzaak om een attribuut als een expressie te evalueren, en het opnieuw te laten evalueren wanneer de expressie verandert. Dynamische invoer!

We willen dit kunnen doen…

<dynamicinput in="outervariable"></dynamicinput>

…en de evaluatie van outervariable doorgeven aan dynamicinput.

Vóór AngularJS 1.5, was de enige syntaxis die we hiervoor hadden =:

app.component("dynamicinput",{
bindings: { in: '=' },
template: '<p>dynamic input: <strong>{{$ctrl.in}}</strong></p>'
});

Het nadeel van = was dat het een twee-weg data binding creëerde, terwijl we slechts een een-weg nodig hadden. Dit betekende ook dat de expressie die we doorgeven een variabele moet zijn.

Maar met AngularJS 1.5 kregen we <, wat eenrichtingsgegevensbinding betekent. Hierdoor kunnen we elke expressie als invoer gebruiken, zoals een functie-aanroep:

<dynamicinput in="calculateSomething()"></dynamicinput>

De component-implementatie zou precies hetzelfde zijn, behalve het veranderen van = voor <.

Uitgang opvangen

Tijd om de zaken om te draaien – hoe vangen we uitvoer van een component op? Zie de kleine app hieronder – de knoppen worden weergegeven in een kind, en wanneer ze worden aangeklikt willen we de buitenste waarde dienovereenkomstig bijwerken.

Dit is waar & in het spel komt. Het interpreteert het attribuut als een verklaring en verpakt het in een functie. De component kan die functie dan naar believen aanroepen, en variabelen in het statement vullen. Uitvoer naar de ouder!

Als onze buitenste html er zo uitziet…

Outer value: {{count}}
<output out="count = count + amount"></output>

..dan zou een implementatie van output met behulp van & er zo uit kunnen zien:

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> `
});

Merk op hoe we een object doorgeven met de variabelen om te vullen. Deze ingewikkelde syntax betekent dat om een component met uitvoer te gebruiken, we twee dingen moeten weten:

  • de attribuutnaam of -namen die we moeten gebruiken
  • de namen van de variabelen die op magische wijze zullen worden aangemaakt.

Omdat & zo ingewikkeld is, gebruiken velen = om uitvoer te doen. Door de variabele in te voeren die gemanipuleerd moet worden…

Outer value: {{count}}
<output out="count"></output>

…veranderen we die variabele in de component:

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>`
});

Dit is echter niet erg mooi:

  • we doen weer aan twee-weg data binding terwijl we maar een manier nodig hebben
  • we willen de uitvoer misschien niet opslaan, maar er gewoon op reageren

Een mooiere oplossing dan al het bovenstaande is om < te gebruiken om uitvoer te maken door een callback door te geven!

We maken de callback in de buitencontroller…

$scope.callback = function(amount){
$scope.count += amount;
}

…en geven die door aan de component:

<output out="callback"></output>

De component roept hem nu eenvoudigweg aan:

app.component("output",{
bindings: { out: '<' },
template: `
<button ng-click="$ctrl.out(1)">buy one</button>
<button ng-click="$ctrl.out(5)">buy many</button>`
});

Zeer vergelijkbaar met &, maar dan zonder de ingewikkelde magie!

Als interessante bijkomstigheid, dit patroon is precies hoe output van een component werkt in React.

Twee-weg data binding

Dit is waar = meestal mag schitteren als AngularJS poster boy. Neem deze app:

Als we het zo renderen…

Outer: <input ng-model="value">
<twoway connection="value"></twoway>

…dan kunnen we twoway implementeren met = zoals dit:

app.component("twowayeq",{
bindings: { connection: '=' },
template: `inner: <input ng-model="$ctrl.connection">`
});

Makkelijk weliswaar, maar let op – het is vrij zeldzaam om twee-weg data binding nodig te hebben. Vaak is wat je eigenlijk wilt een input en een output.

Dat brengt ons bij hoe we twee-weg binding kunnen implementeren met alleen <! Als we opnieuw een callback functie in de buitenste controller maken…

$scope.callback = function(newval){
$scope.value = newval;
}

…en zowel de waarde als de callback doorgeven…

<twoway value="value" callback="callback"></twoway>

…dan kan de component zo worden gemaakt:

app.component("twowayin",{
bindings: {
value: '<',
callback: '<'
},
template: `
<input ng-model="$ctrl.value" ng-change="$ctrl.callback($ctrl.value)">
`
});

We hebben twee-weg data binding bereikt, maar we houden ons nog steeds aan een een-richtings data stroom. Beter karma!

De symbolen helemaal achterwege laten

De vier symbolen zijn eigenlijk maar snelkoppelingen. We kunnen alles wat ze doen ook zonder ze doen.

De string passing app…

…die we zo hebben weergegeven…zou kunnen worden geïmplementeerd door toegang tot de $element service:

app.component("readingstring", {
controller: function($element){
this.text = $element.attr("text");
},
template: '<p>text: <strong>{{$ctrl.text}}</strong></p>'
});

Of met een richtlijn, door gebruik te maken van de attrs die worden doorgegeven aan 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>'
};
});

De dynamische input app…

…zo weergegeven…

<dynamicinput in="outervariable"></dynamicinput>

…zou kunnen worden geïmplementeerd door een .$watch aanroep te gebruiken in het bovenliggende bereik:

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>'
});

De uitvoer app…

…zo weergegeven…

<output out="count = count + amount"></output>

…zou kunnen worden geïmplementeerd door $scope.$apply aan te roepen in het bovenliggende bereik:

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>`
});

Dit is niet precies hetzelfde als & omdat we nu ook het bovenliggende bereik hebben vervuild met een amount variabele, maar toch, het laat het concept goed genoeg zien.

Eindelijk kan de twee-weg app…

…weergegeven worden zoals met =…zou kunnen worden geïmplementeerd door het instellen van een $watch in zowel ouder als kind scope:

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">`
});

Dit is een beetje valsspelen omdat we nu aannemen dat de gebonden waarde altijd een string is, maar, de essentie is er nog steeds!

Wrapping up

We hopen dat deze reis leerzaam is geweest, en dat @, <, = en & nu minder intimiderend aanvoelen.

En dat het u is opgevallen hoe < de kont van de rest schopt! Het kan alles, wat = ook kan, maar < ziet er veel beter uit om het te doen.

Beiden zijn wat onhandig voor het lezen van strings (< vereist een string in een string, en = heeft een proxy-variabele nodig), maar dat is gemakkelijk genoeg om vanille te doen, dus @ moet niet te verwaand worden.

Ook kan & op een stokje gaan draaien.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.