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 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:

angular.module("MyApp", [])
    .directive("selectableList", () => {

        const template = `
            <ul>
                <li
                    ng-repeat="item in items track by item.id"
                >
                    <input
                        type="checkbox"
                        ng-model="item.$selected"
                        ng-change="updateSelected()"
                    >
                    <span ng-bind="item.name"></span>
                </li>
            </ul>`;

        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á).

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:

<selectable-list
    items="items"
    ng-model="selected"
></selectable-list>

El cual funcionará dentro de un formulario como cualquier campo mas, por ejemplo, podemos hacer que sea requrido:

<selectable-list
    ng-required="true"
    items="items"
    ng-model="selected"
></selectable-list>

Puede ver un ejemplo funcionando acá: http://embed.plnkr.co/sUKn485fPQrcDO2Q4Z51/