Nella lezione precedente abbiamo definito una direttiva per la selezione di una città che soffre però alcune limitazioni, dovute al legame che ha con lo scope del controller della view in cui è contenuta. Ciò comporta alcuni vincoli di utilizzo:
- siamo costretti a creare nel controller una variabile di scope con il nome
elencoCitta
esattamente con la struttura prevista dalla direttiva, cioè un array di oggetti con le proprietàcodice
enome
. - non è possibile utilizzare due istanze della nostra direttiva nella stessa
view
su elenchi di città diverse.
In altre parole, allo stato attuale la nostra direttiva non ha le caratteristiche tipiche di un componente che può essere liberamente riutilizzato in ambiti diversi.
Per garantire una certa indipendenza delle direttive dalle view e dai controller in cui vengono utilizzate, Angular introduce il concetto di scope isolato (isolated scope), cioè di uno scope legato alla direttiva e mappabile con valori provenienti dall'esterno.
La creazione di uno scope per la direttiva è affidata alla proprietà scope
, come mostrato di seguito:
angular.module("myApp", [])
.directive("mySelectCity", function() {
return {
restrict: "E",
templateUrl: "/mySelectCityTemplate.html",
scope: {
elencoCitta: "=cityList"
}
};
});
Come possiamo vedere, abbiamo assegnato alla proprietà scope
della direttiva un oggetto che definisce le mappature tra le variabili interne e quelle esterne. Nello specifico, la proprietà elencoCitta
indica la variabile interna e la stringa =cityList
assegnatale rappresenta l'aggancio con la variabile esterna. Con la definizione di questa mappatura, abbiamo introdotto un attributo city-list
per la nostra direttiva che verrà sfruttato per specificare la variabile di scope esterna.
Alla luce di queste modifiche, avremo che la nostra direttiva verrà utilizzata nella view nel seguente modo:
<div ng-controller="myController">
<my-select-city city-list="elencoCitta"></my-select-city>
</div>
;
Grazie alla mappatura che abbiamo definito, la variabile elencoCitta
dello scope del controller sarà mappata sulla variabile omonima dello scope della direttiva tramite l'attributo city-list
. Quindi a questo punto siamo liberi di cambiare il nome sia alla variabile di scope del controller che alla variabile di scope della direttiva. Infatti sarà l'attributo city-list
a mantenere attiva la mappatura tra i due scope. Non solo, ma saremo liberi anche di mappare elenchi di città diverse ad istanze diverse della stessa direttiva senza rischio di conflitti:
<div ng-controller="myController">
<my-select-city city-list="elencoCapoluoghiRegione"></my-select-city>
<my-select-city city-list="elencoCapoluoghiProvincia"></my-select-city>
</div>
Ad essere precisi, rimarrebbe ancora un altro vincolo alla completa indipendenza della nostra direttiva dal modello dei dati definito nel controller: i nomi delle proprietà codice
e nome
della singola città da utilizzare per il binding delle option
nella select
.
Possiamo gestire anche questa situazione effettuando il seguente refactoring della soluzione inizialmente proposta:
angular.module("myApp", [])
.directive("mySelectCity", function() {
return {
restrict: "E",
templateUrl: "/mySelectCityTemplate.html",
scope: {
cityList: "=",
cityDisplayProperty: "="
}
};
});
<script type="text/ng-template" id="/mySelectCityTemplate.html">
<select ng-model="selectedItem"
ng-options="city[cityDisplayProperty] for city in cityList">
</select>
</script>
Le modifiche rispetto alla soluzione iniziale sono diverse. Innanzitutto abbiamo ridefinito il template della direttiva sfruttando ng-options
al posto di ng-repeat
. Questo ci consente di rendere più flessibile la soluzione e di limitare ad una sola proprietà necessaria per il binding delle option
.
Proprio per il binding, abbiamo parametrizzato il nome della proprietà mappandola su un nuovo attributo della nostra direttiva: city-display-property
.
Da notare come nella mappatura tra "proprietà dello scope della direttiva" e "attributo" abbiamo riportato semplicemente la stringa ="..."
invece della stringa che riportava lo stesso nome preceduto dall'operatore =
. In effetti, quando il nome della proprietà dello scope è corrispondente al nome dell'attributo possiamo anche semplicemente specificare l'operatore =
; in altre parole, gli assegnamenti cityDisplayProperty: "=cityDisplayProperty"
e cityDisplayProperty: "="
hanno lo stesso significato.
In conclusione, la nostra direttiva sarà utilizzata nel seguente modo:
<div ng-controller="myController">
<my-select-city city-list="elencoCitta" city-display-property="'nome'">
</my-select-city>
</div>
Con questo accorgimento la nostra direttiva è totalmente indipendente dalle scelte effettuate nel controller per assegnare i nomi delle variabili. Lo scope
isolato e gli attributi della direttiva ci permettono di mappare i dati in maniera molto semplice ed efficace.