Simple CRUD con angular-material and lodash.

View online demo:
http://ba3-simplecrud-array.azurewebsites.net/app/index.html
.full-heigth {
    height: -moz-calc(100% - 64px); 
    height: -webkit-calc(100% - 64px); 
    height: calc(100% - 64px);
}

.dialog-95 {
    width: 95%;
    height: auto;
}
(function () {
    'use strict';
    var app = angular.module("simpleCRUD", ['ngMessages', 'ngMaterial']);
    //Registering icon set
    app.config(iconConfiguration);

    function iconConfiguration($mdIconProvider) {
        $mdIconProvider.defaultIconSet('icons_24x24.svg', 24);
    }
})();
(function () {
    'use strict';
    var app = angular.module('simpleCRUD');
    app.config(override_mdMaxlength);

    //Sobreescribiendo md-maxlength (Reference: Jesús Rodríguez Rodríguez, http://tinyurl.com/o9xhnvg)
    function override_mdMaxlength($provide) {
        $provide.decorator(
            'mdMaxlengthDirective',
            function ($delegate) {
                var mdMaxlength = $delegate[0];
                var link = mdMaxlength.link;
                mdMaxlength.compile = function () {
                    return function (scope, element, attr, ctrls) {
                        var maxlength;
                        var ngModelCtrl = ctrls[0];
                        var containerCtrl = ctrls[1];
                        var charCountEl = angular.element('<div class="md-char-counter">');

                        attr.$set('ngTrim', 'false');
                        containerCtrl.element.append(charCountEl);

                        ngModelCtrl.$formatters.push(renderCharCount);
                        ngModelCtrl.$viewChangeListeners.push(renderCharCount);
                        element.on(
                            'input keydown',
                            function () {
                                renderCharCount(); //make sure it's called with no args
                            }
                        );

                        scope.$watch(attr.mdMaxlength, function (value) {
                            maxlength = value;
                            if (angular.isNumber(value) && value > 0) {
                                if (!charCountEl.parent().length) {
                                    $animate.enter(
                                        charCountEl,
                                        containerCtrl.element,
                                        angular.element(containerCtrl.element[0].lastElementChild)
                                    );
                                }
                                renderCharCount();
                            } else {
                                $animate.leave(charCountEl);
                            }
                        });

                        ngModelCtrl.$validators['md-maxlength'] = function (modelValue, viewValue) {
                            if (!angular.isNumber(maxlength) || maxlength < 0) {
                                return true;
                            }
                            return (modelValue || element.val() || viewValue || '').length <= maxlength;
                        };

                        function renderCharCount(value) {
                            charCountEl.text((ngModelCtrl.$modelValue || '').length + '/' + maxlength);
                            return value;
                        }
                    };
                };
                return $delegate;
            }
        );
    }
})();
<!DOCTYPE html>
<md-dialog aria-label="Editar" class="dialog-95">
    <form name="inputForm" novalidate>
        <md-toolbar>
            <div class="md-toolbar-tools">
                <md-button class="md-icon-button" ng-click="regresar()">
                    <md-icon md-svg-icon="white_back" aria-label="Regresar"></md-icon>
                    <md-tooltip md-delay="1500" md-autohide="true">Regresar</md-tooltip>
                </md-button>
                <h2>{{view.operacion}}</h2>
                <span flex></span>
                <md-button class="md-icon-button" ng-click="borrar()" ng-show="view.operacion === 'Modificar'">
                    <md-icon md-svg-icon="white_delete" aria-label="Borrar"></md-icon>
                    <md-tooltip md-delay="1500" md-autohide="true">Borrar</md-tooltip>
                </md-button>
                <md-button class="md-icon-button" ng-click="guardar()" ng-show="view.operacion === 'Agregar' || view.operacion === 'Modificar'" ng-disabled="inputForm.$invalid">
                    <md-icon md-svg-icon="white_save" aria-label="Guardar"></md-icon>
                    <md-tooltip md-delay="1500" md-autohide="true">Guardar</md-tooltip>
                </md-button>
            </div>
        </md-toolbar>
        <md-dialog-content>
            <md-content layout-padding ng-cloak>
                <md-input-container>
                    <label>ID (solo lectura)</label>
                    <input type="text" readonly name="id" ng-model="view.selectedItem.id" />
                </md-input-container>
                <md-input-container>
                    <label>Name</label>
                    <input type="text" name="name" required md-maxlength="50" ng-model="view.selectedItem.name" />
                    <ng-messages for="inputForm.name.$error">
                        <ng-message when="required">El nombre es obligatorio</ng-message>
                        <ng-message when="md-maxlength">Use 50 caracteres o menos para el nombre</ng-message>
                    </ng-messages>
                </md-input-container>
                <md-input-container>
                    <label>Last name</label>
                    <input type="text" name="lastname" md-maxlength="50" ng-model="view.selectedItem.lastname" />
                    <ng-messages for="inputForm.lastname.$error">
                        <ng-message when="md-maxlength">Use 50 caracteres o menos para el nombre</ng-message>
                    </ng-messages>
                </md-input-container>
                <md-input-container>
                    <label>Email</label>
                    <input type="text" name="email" required ng-model="view.selectedItem.email" ng-pattern="/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i">
                    <ng-messages for="inputForm.email.$error">
                        <ng-message when="required">El correo es obligatorio</ng-message>
                        <ng-message when="pattern">Email inválido</ng-message>
                    </ng-messages>
                </md-input-container>
                <md-input-container>
                    <label>Direction</label>
                    <textarea rows="2" name="direccion" md-maxlength="100" ng-model="view.selectedItem.direction"></textarea>
                    <ng-messages for="inputForm.direccion.$error">
                        <ng-message when="md-maxlength">Use 100 caracteres o menos para la dirección</ng-message>
                    </ng-messages>
                </md-input-container>
            </md-content>
        </md-dialog-content>
    </form>
</md-dialog>
<svg 
     width="24"
     height="24" 
     viewBox="0 0 24 24" 
     xmlns="http://www.w3.org/2000/svg">
    <g id="white_person_add">
        <path d="M0 0h24v24H0z" fill="none" />
        <path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
    </g>
    <g id="white_delete">
        <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
        <path d="M0 0h24v24H0z" fill="none" />
    </g>
    <g id="white_edit">
        <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
        <path d="M0 0h24v24H0z" fill="none" />
    </g>
    <g id="white_save">
        <path d="M0 0h24v24H0z" fill="none" />
        <path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z" />
    </g>
    <g id="white_back">
        <path d="M0 0h24v24H0z" fill="none" />
        <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
    </g>
    <g id="white_clear">
        <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
        <path d="M0 0h24v24H0z" fill="none" />
    </g>
    <g id="white_favorite">
        <path d="M0 0h24v24H0z" fill="none" />
        <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
    </g>
    <g id="white_favorite_border">
        <path d="M0 0h24v24H0z" fill="none" />
        <path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z" />
    </g>
    <g id="white_select_all">
        <path d="M0 0h24v24H0z" fill="none" />
        <path d="M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z" />
    </g>
    <g id="white_classification">
        <path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z" />
        <path d="M0 0h24v24H0z" fill="none" />
    </g>
    <g id="white_order">
        <path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" />
        <path d="M0 0h24v24H0z" fill="none" />
    </g>
</svg>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" ng-app="simpleCRUD">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />

    <title>Simple CRUD Angular Material</title>

    <link rel="icon" href="favicon.ico" type="image/x-icon" />
    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />

    <link href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/0.10.1/angular-material.css" rel="stylesheet" />
    <link href="app.css" rel="stylesheet" />

    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular-animate.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular-aria.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular-messages.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-material/0.10.1/angular-material.js" type="text/javascript"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js" type="text/javascript"></script>

    <script src="app.js" type="text/javascript"></script>
    <script src="custom-angular-material.js" type="text/javascript"></script>
    <script src="indexController.js" type="text/javascript"></script>
</head>
<body ng-controller="SimpleCRUDController">
    <md-toolbar>
        <div class="md-toolbar-tools">
            Simple CRUD Angular Material
            <span flex></span>
            <md-menu>
                <md-button class="md-icon-button" aria-label="Orden" ng-click="abrirMenu($mdOpenMenu, $event)">
                    <md-icon md-svg-icon="white_order"></md-icon>
                    <md-tooltip md-delay="1500" md-autohide="true">Orden</md-tooltip>
                </md-button>
                <md-menu-content width="4">
                    <md-menu-item>
                        <md-button class="md-button" ng-click="ordenarPor('+')">Ascendente</md-button>
                    </md-menu-item>
                    <md-menu-divider></md-menu-divider>
                    <md-menu-item>
                        <md-button class="md-button" ng-click="ordenarPor('-')">Descendente</md-button>
                    </md-menu-item>
                </md-menu-content>
            </md-menu>
            <md-menu>
                <md-button class="md-icon-button" aria-label="Clasificar" ng-click="abrirMenu($mdOpenMenu, $event)">
                    <md-icon md-svg-icon="white_classification"></md-icon>
                    <md-tooltip md-delay="1500" md-autohide="true">Clasificar</md-tooltip>
                </md-button>
                <md-menu-content width="4">
                    <md-menu-item>
                        <md-button class="md-button" ng-click="clasificarPor('name')">Name</md-button>
                    </md-menu-item>
                    <md-menu-divider></md-menu-divider>
                    <md-menu-item>
                        <md-button class="md-button" ng-click="clasificarPor('lastname')">Last name</md-button>
                    </md-menu-item>
                    <md-menu-divider></md-menu-divider>
                    <md-menu-item>
                        <md-button class="md-button" ng-click="clasificarPor('email')">Email</md-button>
                    </md-menu-item>
                    <md-menu-divider></md-menu-divider>
                    <md-menu-item>
                        <md-button class="md-button" ng-click="clasificarPor('direction')">Direction</md-button>
                    </md-menu-item>
                </md-menu-content>
            </md-menu>
        </div>
    </md-toolbar>
    <md-content class="full-heigth">
        <md-list>
            <md-list-item class="md-3-line" ng-repeat="element in view.dataTable | orderBy:view.order.orderby + view.order.classification" ng-click="mostrarDialogo('R', element, $event)">
                <div class="md-list-item-text">
                    <h3>{{element.lastname + ", " + element.name}}</h3>
                    <h4>{{element.email}}</h4>
                    <p>{{element.direction}}</p>
                </div>
                <md-icon aria-label="Editar" class="md-secondary" md-svg-icon="white_edit" ng-click="mostrarDialogo('UD', element, $event)"></md-icon>
                <md-divider></md-divider>
            </md-list-item>
        </md-list>
    </md-content>
    <md-button class="md-fab md-fab-bottom-right" aria-label="Nuevo">
        <md-icon md-svg-icon="white_person_add" ng-click="mostrarDialogo('C', undefined, $event)"></md-icon>
    </md-button>
</body>
</html>
(function () {
    'use strict';
    var app = angular.module('simpleCRUD');
    app.controller('SimpleCRUDController', ['$scope', '$mdToast', '$mdDialog', SimpleCRUDController]);

    function SimpleCRUDController($scope, $mdToast, $mdDialog, $mdOpenMenu) {
        //Data de prueba
        $scope.view = {
            dataTable: [
                { "id": "570c9478-5fc8-4c33-82be-19184ada466c", "name": "Willie", "lastname": "Andrews", "email": "wandrews0@myspace.com", "direction": "4382 Hagan Drive" },
                { "id": "f74b4996-9d6f-4b1a-97c3-912c159d9c5b", "name": "Gloria", "lastname": "Nelson", "email": "gnelson1@youku.com", "direction": "09 Goodland Lane" },
                { "id": "39acb9d4-b313-472e-90f6-4afd8cf3cac3", "name": "Jeremy", "lastname": "Reyes", "email": "jreyes2@addthis.com", "direction": "94451 Talmadge Place" },
                { "id": "3aea0235-8381-49b7-b8e6-50113d0af988", "name": "Daniel", "lastname": "Gray", "email": "dgray3@dedecms.com", "direction": "36 Hagan Place" },
                { "id": "8099daf8-2210-4d34-9aaa-2e0d714503c4", "name": "Albert", "lastname": "Wood", "email": "awood4@state.gov", "direction": "29775 Hagan Way" },
                { "id": "edfc944b-f87e-438e-b122-05e95d7ed59a", "name": "Kevin", "lastname": "Tucker", "email": "ktucker5@pagesperso-orange.fr", "direction": "59762 Farmco Junction" },
                { "id": "346871de-6172-4baf-93aa-955282523038", "name": "Maria", "lastname": "Ferguson", "email": "mferguson6@mediafire.com", "direction": "8566 Fisk Trail" },
                { "id": "72afca23-368b-490a-95b6-6494202814ce", "name": "Charles", "lastname": "Grant", "email": "cgrant7@youtu.be", "direction": "726 Mariners Cove Point" },
                { "id": "98ea2793-4c82-4bad-98e9-d5dfff723fdf", "name": "Johnny", "lastname": "Thompson", "email": "jthompson8@google.ca", "direction": "08 Dahle Plaza" },
                { "id": "38e9f6f6-615e-4ff9-8c8a-06e0d1b24ae9", "name": "John", "lastname": "Olson", "email": "jolson9@earthlink.net", "direction": "71 Dapin Trail" },
                { "id": "d4d8e591-53ff-4261-a2fe-d5a37928458a", "name": "Virginia", "lastname": "Martin", "email": "vmartina@apache.org", "direction": "58304 Nancy Point" },
                { "id": "53703796-496f-4b93-8ec0-c92f783aa6bc", "name": "Carol", "lastname": "Gonzales", "email": "cgonzalesb@blogger.com", "direction": "522 Pankratz Lane" }
            ],
            order: {
                classification: 'lastname',
                orderby: '+'
            }
        };

        //Definicion de metodos
        $scope.ordenarPor = ordenarPor;
        $scope.clasificarPor = clasificarPor;
        $scope.abrirMenu = abrirMenu;
        $scope.mostrarDialogo = mostrarDialogo;
        
        
        //Referencia: http://tinyurl.com/p8ylr8n
        function generateUUID() {
            var d = new Date().getTime();
            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = (d + Math.random()*16)%16 | 0;
                d = Math.floor(d/16);
                return (c=='x' ? r : (r&0x3|0x8)).toString(16);
            });
            return uuid;
        }

        //Muestra un mensaje toast (funcion base)
        function simpleToastBase(message, position, delay, action) {
            $mdToast.show(
                $mdToast.simple()
                    .content(message)
                    .position(position)
                    .hideDelay(delay)
                    .action(action)
            );
        }

        //Muestra un mensaje toast de error
        function mostrarError(mensaje) {
            simpleToastBase(mensaje, 'bottom right', 6000, 'X');
        }

        //Ordena los elementos de forma ascendente o descendente
        function ordenarPor(sign) {
            $scope.view.order.orderby = sign;
        }

        //Ordena los elementos por nombre de propiedad
        function clasificarPor(field) {
            $scope.view.order.classification = field;
        }

        //Abre el menu de opciones
        function abrirMenu(mdOpenMenu, event) {
            mdOpenMenu(event);
        }


        //Muestra un cuadro de dialogo
        function mostrarDialogo(operacion, data, event) {
            //Guardando los datos a enviar
            var tempData = undefined;
            if (data === undefined) {
                tempData = {};
            } else {
                tempData = {
                    id: data.id,
                    name: data.name,
                    lastname: data.lastname,
                    email: data.email,
                    direction: data.direction

                };
            }
            $mdDialog.show({
                templateUrl: 'editor.html',
                targetEvent: event,
                locals: {
                    selectedItem: tempData,
                    dataTable: $scope.view.dataTable,
                    operacion: operacion
                },
                bindToController: true,
                controller: DialogController,
                parent: angular.element(document.body)
            })
            .then(
                function (result) {
                    mostrarError(result);
                }
            );
        }

        //Controller de cuadro de dialogo
        function DialogController($scope, $mdDialog, operacion, selectedItem, dataTable) {
            $scope.view = {
                dataTable: dataTable,
                selectedItem: selectedItem,
                operacion: 'Agregar'
            };

            //Determinando el tipo de operacion que es
            switch (operacion) {
                case 'C':
                    $scope.view.operacion = 'Agregar';
                    break;
                case 'UD':
                    $scope.view.operacion = 'Modificar';
                    break;
                case 'R':
                    $scope.view.operacion = 'Detalles';
                    break;
                default:
                    $scope.view.operacion = 'Detalles';
                    break;
            }

            //Metodos del controller del cuadro de dialogo
            $scope.regresar = regresar;
            $scope.guardar = guardar;
            $scope.borrar = borrar;


            //Regresa a la ventana principal sin realizar accion alguna
            function regresar() {
                $mdDialog.cancel();
            }


            //Selecciona la opcion de agregar un elemento nuevo o modificar uno existente
            function guardar() {
                if ($scope.view.selectedItem.id === undefined) agregar();
                else modificar();
            }

            //Permite agregar un nuevo elemento
            function agregar() {
                //Determinando si existe el elemento con el ID especificado
                var temp = _.find($scope.view.dataTable, function (x) { return x.id === $scope.view.selectedItem.id; });
                if (temp === undefined) {
                    //Generando ID para el nuevo elemento
                    $scope.view.selectedItem.id = generateUUID();
                    $scope.view.dataTable.push($scope.view.selectedItem);
                    $mdDialog.hide('Datos agregados con éxito');
                } else {
                    $mdDialog.hide('Ya están registrados los datos de la persona indicada');
                }
            }

            //Permite modificar un registro
            function modificar() {
                //Determinando si existe el elemento con el ID especificado
                var index = _.findIndex($scope.view.dataTable, { 'id': $scope.view.selectedItem.id });
                if (index !== -1) {
                    $scope.view.dataTable[index].name = $scope.view.selectedItem.name;
                    $scope.view.dataTable[index].lastname = $scope.view.selectedItem.lastname;
                    $scope.view.dataTable[index].email = $scope.view.selectedItem.email;
                    $scope.view.dataTable[index].direction = $scope.view.selectedItem.direction;
                    $mdDialog.hide('Datos modificados con éxito');
                } else {
                    $mdDialog.hide('No se pudo modificar los datos de la persona seleccionada');
                }
            }

            //Permite eliminar un registro
            function borrar() {
                var item = _.find($scope.view.dataTable, function (x) { return x.id === $scope.view.selectedItem.id; });
                if (item !== undefined) {
                    _.pull($scope.view.dataTable, item);
                    $mdDialog.hide('Datos borrados con éxito');
                } else {
                    $mdDialog.hide('No se pudo borrar los datos de la persona seleccionada');
                }
            }
        }
    }
})();