# Creando una directiva en Angular que soporte ```ng-model```
Esto es simple, pero buscando en internet me di cuenta que la documentación que hay es mala, y en español sobretodo, inexistente.
Lo que vamos a hacer es crear una directiva de angular que soporte manejar un modelo con ```ng-model```, lo que me sorprende de esto es que buscando el primer resultado es una pregunta en StackOverflow llamada; [Create a directive that uses ng-model](https://stackoverflow.com/questions/14115701/angularjs-create-a-directive-that-uses-ng-model) donde la respuesta ganadora tiene 172 votos, pero personalmente diserto de esta respuesta, ya que no considero que conteste a la pregunta.
¿Por qué no usar esa respuesta? Porque queremos tener las ventajas de usar ```ng-models```, como el poder validarlo dentro de un formulario.
En mi caso en particular, tuve que hacer un listado dentro de un formulario, en el que cada item puede ser seleccionado y esto afecta a los que se va a enviar al final. No voy a entrar en detalle de como funciona esto, así que solo me centraré en mostrar como definir el modelo.
Lo primero es modificar nuestra directiva para requerir el [```NgModelController```](https://docs.angularjs.org/api/ng/type/ngModel.NgModelController#!):
angular.module("MyApp", [])
.directive("selectableList", () => {
const template = `
`;
return {
strict: "EA",
replace: true,
template: template,
require: '?ngModel',
scope: {
items: "=",
},
link (scope, el, attrs, ngModel) {
// scope.items es definida por el usuario y
// ngModel es una instancia de ngModelController o null
}
};
});
Ahora hay que tener en cuenta, ```ngModel``` está requerido como opcional, por lo que puede ser ```null```.
Otra cosa a tener en cuenta, es que inicialmente ```ngModel.$modelValue``` y ```ngModel.$viewValue``` nos puede venir con el valor ```NaN```, para solucionar esto vamos a incluir un iniciador (algo que saqué de [acá](https://stackoverflow.com/questions/16383767/model-modelvalue-is-nan-in-directive#25448464)).
En fin dentro de la función link:
var unregisterInit, selectedItems = [];
// Cada vez que se seleccione o des-seleccione un
// elemento, actualizamos el modelo
scope.updateSelected = function(noDirty = false) {
// Filtramos solo los items seleccionados
selectedItems = scope.items.filter(i => i.$selected);
if (ngModel) {
ngModel.$setViewValue(selectedItems)
// Si no se nos indica lo contrario, marcamos el
// modelo como $dirty y $touched, esto es para
// implemnetar con formularios
if (!noDirty) {
ngModel.$setDirty();
ngModel.$setTouched();
}
}
};
const init = function(value) {
// Nos des-suscribimos del primer $watch
unregisterInit();
// Si tenemos ngModel, le definimos el valor de los elementos
// Seleccionados
if (ngModel) {
selectedItems = value.map((i) => {
i.$selected = true;
return i;
});
ngModel.$setViewValue(selectedItems);
}
// En caso de que se actualice "items", actualizamos el modelo
scope.$watchCollection("items", () => {
scope.updateSelected(true);
});
// Nullify
value = null;
};
// Nos subscribimos al primer cambio del scope
unregisterInit = scope.$watch(() => {
var value;
// Si tenemos ngModel, tomamos su valor
if (ngModel) {
value = ngModel.$modelValue;
}
// Si no lo definimos antes, o es null, lo definimos
// como un array vacío
if (!value) {
value = [];
}
// Se lo mandamos a init()
return value;
}, init);
Ahora tenemos nuestra directiva de un listado de elementos seleccionables los cuales podemos usar:
El cual funcionará dentro de un formulario como cualquier campo mas, por ejemplo, podemos hacer que sea requrido:
Puede ver un ejemplo funcionando acá:
http://embed.plnkr.co/sUKn485fPQrcDO2Q4Z51/