Simple To Do App with MEAN Stack Comparison

In this example, we use a plain old Moxie.Build system and compare it to the MEAN Stack equivalent by building two functionally equivalent versions of a fleshed out To Do List Application. Both versions utilize local user authentication, a relational database system and the security measures found in typical production-level applications on the web today.

Setup Required:

A single Moxie.Build installation consists of one folder containing all of the tools that you will need to build typical web software from the ground up. Moxie.Build works similarily to a server-side rendered application where multiple web pages/views can be rendered from a single procedure.

  • Time to Learn: 6 Weeks
  • Time to Build: 6 Hours
  • Lines of Code: 381
  • Byte Count: 12237
Setup Required:

A strong understanding of a modern MVC JavaScript Framework and NodeJS is essential to building MEAN Stack web applications. In this particular example, I used AngularJS, MongoDB, NodeJS, LoopBack in conjunction with a large number of external modules for both NodeJS and AngularJS.

  • Time to Learn: 1 Year
  • Time to Build: 10 Hours
  • Lines of Code: 648
  • Byte Count: 20095

MEAN stack development is great because there is such a large JavaScript community on the web who are working together to create open source Frameworks and modules so you will always have a great number of options to choose from when you are deciding how to build/structure your application. Having options can be great, but how can you know that you are choosing the right tools to use. Additionally, problems can often arise when a particular module that you have been using forever becomes no longer supported or is no longer compatible with one of the frameworks that are you using. As time goes by, you will find yourself relearning new technologies/frameworks to accomplish the same tasks with slightly less code or with a new style of architecture.

Moxie.Build development is also great because a lot of the more complicated and tedious programming tasks are baked right into the system. There is no longer a need to build a custom CMS for each and every collection of data so that your client can easily manage it without your help. Additionally, a large part of the development and required configuration/setup of your web software is easily accessible/maintainable through the use of GUIs which makes your application simple-stupid-easy to set up and configure. Moxie allows you to spend more time on providing the business related functionality of your software and less time performing repetitive and tedious tasks that are required for typical web software these days.

In conclusion, whether you are a seasoned veteran of MEAN stack development, or you are still a Web Programming newbie, Moxie.Build offers a number of solutions to both types of developers. If you're tired of learning a new MVC Framework every two years or if you're fed up of creating custom CMS admin interfaces, try Moxie.Build and prepared to be bedazzled when you discover how simple it can be to build Web Software.



Screenshots of the To Do Application to give you a better idea of what the code is accomplishing.


A single procedure to build all of the HTML and encapsulated logic for the entire application.


Rem 'Init
    HttpNoCache
    HttpsEnsure
EndRem

Rem 'Flow Control
    [Pull]  Req.IsPost List.Edit Item.Edit Item.Del List.Del
    
    If      Req.IsPost  : And List.Edit     : And Not Item.Edit : List.Edit.Proc    List.Edit
    ElseIf                    List.Edit     : And Not Item.Edit : List.Edit.Show    List.Edit, ""
    
    ElseIf  Req.IsPost  : And List.Edit     : And Item.Edit     : Item.Edit.Proc    List.Edit, Item.Edit
    ElseIf                    List.Edit     : And Item.Edit     : Item.Edit.Show    List.Edit, Item.Edit, ""
    
    ElseIf  Req.IsPost  : And Item.Del      : Item.Del.Proc     Item.Del
    ElseIf                    Item.Del      : Item.Del.Show     Item.Del
    
    ElseIf Req.IsPost   : And List.Del      : List.Del.Proc     List.Del
    ElseIf                    List.del      : List.Del.Show     List.Del
    
    Else                                    : List.All
    End If
EndRem


Method List.All()
    Rem 'Get our data
        SetNew      "MemTab.Person.Alias", ($SessionUser)
        Children    "ToDo.List"
    EndRem
    
    Html "
" HtmlAButton "MainBody", "Append", " Add List", "?List.Edit=*", "&default &md" Html "
"
'Build a Panel with each List Item ForEach "Me.Output", "List.All.forList" End Method Method List.All.forList() Rem 'Get data from top query [New] ListName | ToDo.List.Name [New] List | ToDo.List.Alias If Not List Then Exit Children "ToDo.Item" EndRem Rem 'Customize some fields WorkWith "ToDo.Item" Build "Name", ("", "Name", "", "Alias" NewFields "Delete" Build "Delete", ((NbSp$ 4) & "", "Alias" End WorkWith KeepFields "ToDo.Item.", "Name DueDate Status Delete" NameField "ToDo.Item.Delete", "Delete" EndRem Rem 'Build Panel Prefix Html "
" "
" ("

" & ListName & "

"
) End Prefix Html "
"
Html "
" Html "
" HtmlAButton "MainBody", "Append", " Edit List", ("?List.Edit=" & List), "&default &sm" HtmlAButton "MainBody", "Append", " Delete List", ("?List.Del=" & List), "&default &sm" Html "
"
HtmlAButton "MainBody", "Append", " Add Item", ("?Item.Edit=*&List.Edit=" & List), "&default &sm" HtmlTable Html "
"
Html "
"
EndRem End Method Method List.Edit.Show(pList, pAnyErr) If pList = * WorkWith "ToDo.List" Pull "Request", "ToDo.List", "Name DueDate" Rem 'Customize form KeepFields "ToDo.List.", "Name DueDate" GetFieldDefs EndRem End WorkWith Else List.IsMine pList 'Security Check LoadRecord "ToDo.List", pList 'Get our data Rem 'Customize form KeepFields "ToDo.List.", "Name DueDate Created Modified" GetFieldDefs EndRem End If If pAnyErr WorkWith "ToDo.List" Pull "Request", "ToDo.List.", "Name DueDate" End WorkWith End If Rem 'Apply err states If pAnyErr [Pull] "List.Edit.Proc.Input", "Name.Err DueDate.Err" Else [New] Name.Err DueDate.Err End If mSetState (Name.Err, "ToDo.List.Name") mSetState (DueDate.Err, "ToDo.List.DueDate") EndRem HtmlForm " Save" 'Put it on the page End Method Method List.Edit.Proc(pList) If pList <> * 'Security Check List.IsMine pList End If Rem 'Validate data [Pull] "Request", "ToDo.List.", "Name DueDate" [New] AnyErr Name.Err DueDate.Err If Not Name : AnyErr = "y" : Name.Err = "y" : HtmlErr "Oops, please provide a name." : End If If DueDate : And Not (ValidDate$ DueDate) : AnyErr = "y" : DueDate.Err = "y" : HtmlErr "Oops, please provide a valid Due Date" : End If If AnyErr List.Edit.Show pList, "y" Exit End If EndRem Name = MCase$ Name 'Data clean up Rem 'Save to database If pList = * SetNew "MemTab.Person.Alias", ($SessionUser) WorkWith "ToDo.List" SetNew "Alias", * Pull "Name DueDate" NewWithAttach "Parent", "MemTab.Person" FailIfRecError [New] nList | ToDo.List.Alias End WorkWith Else WorkWith "ToDo.List" SetNew "Alias", pList Pull "Name DueDate" Backfill Update End Backfill FailIfRecError End WorkWith End If EndRem Rem 'Show all lists again [Pull] Req.Path HttpStatus "302" HttpHeader "Location", ("/" & Req.Path) EndRem End Method Method List.IsMine(pList) Rem 'Search for data via relationships SetNew "MemTab.Person.Alias", ($SessionUser) Children "ToDo.List" KeepIf "ToDo.List.Alias", =, `pList [New] FoundIt | ToDo.List.Alias EndRem Rem 'Handle not found If Not FoundIt HtmlErr "Sorry, it looks like you have an invalid link" List.All Exit Proc End If EndRem End Method Method Item.Edit.Show(pList, pItem, pAnyErr) Rem 'get our data If pItem = * WorkWith "ToDo.Item" Pull "Request", "ToDo.Item", "Name DueDate Status Desc" Rem 'Customize form KeepFields "ToDo.Item.", "Name DueDate Status Desc" GetFieldDefs EndRem End WorkWith Else Item.IsMine pItem 'Security Check LoadRecord "ToDo.Item", pItem 'Get our data Rem 'Customize form KeepFields "ToDo.Item.", "Name DueDate Status Desc Created Modified" GetFieldDefs EndRem End If EndRem Rem 'Apply error states If pAnyErr [Pull] "Item.Edit.Proc.Input", "Name.Err DueDate.Err Status.Err Desc.Err" Else [New] Name.Err DueDate.Err Status.Err Desc.Err End If mSetState (Name.Err, "ToDo.Item.Name") mSetState (DueDate.Err, "ToDo.Item.DueDate") mSetState (Status.Err, "ToDo.Item.Status") mSetState (Desc.Err, "ToDo.Item.Desc") EndRem If pAnyErr WorkWith "ToDo.Item" Pull "Request", "ToDo.Item", "Name DueDate Status Desc" End WorkWith End If HtmlForm " Save" 'Put it on the page End Method Method Item.Edit.Proc(pList, pItem) Rem 'Security Check If pItem <> * Item.IsMine pItem End If EndRem Rem 'validate data [Pull] "Request", "ToDo.Item.", "Name DueDate Status Desc" [New] AnyErr Name.Err DueDate.Err If Not Name : AnyErr = "y" : Name.Err = "y" : HtmlErr "Oops, please provide a name." : End If If DueDate : And Not (ValidDate$ DueDate) : AnyErr = "y" : DueDate.Err = "y" : HtmlErr "Oops, please provide a valid Due Date." : End If If AnyErr Item.Edit.Show pList, pItem, "y" Exit End If EndRem Name = MCase$ Name 'Clean up data Rem 'Save to database If pItem = * SetNew "ToDo.List.Alias", `pList WorkWith "ToDo.Item" SetNew "Alias", * Pull "Name DueDate Status Desc" NewWithAttach "Parent", "ToDo.List" FailIfRecError [New] nItem | ToDo.Item.Alias End WorkWith Else WorkWith "ToDo.Item" SetNew "Alias", `pItem Pull "Name DueDate Status Desc" Backfill Update End Backfill FailIfRecError End WorkWith End If EndRem Rem 'Show all Items again [Pull] Req.Path HttpStatus "302" HttpHeader "Location", ("/" & Req.Path) EndRem End Method Method List.Del.Show(pList) List.IsMine pList 'Security Check Warning.Show "List" 'Display warning End Method Method Item.Del.Show(pItem) Item.IsMine pItem 'Security Check Warning.Show "Item" 'Display warning End Method Method List.Del.Proc(pList) List.IsMine pList 'Security Check Rem 'Delete it SetNew "ToDo.List.Alias", `pList Children "ToDo.Item" DetachWithDelete "ToDo.Item", "Parent", "ToDo.List" FailIfRecError Reset SetNew "ToDo.List.Alias", `pList Parents "ToDo.List", "MemTab.Person" DetachWithDelete "ToDo.List", "Parent", "MemTab.Person" FailIfRecError EndRem List.Redirect 'Return To List End Method Method Item.Del.Proc(pItem) Item.IsMine pItem 'Security Check Rem 'Delete it WorkWith "ToDo.Item" SetNew "Alias", `pItem Parents "ToDo.Item", "ToDo.List" DetachWithDelete "Parent", "ToDo.List" FailIfRecError End WorkWith EndRem List.Redirect 'Return To List End Method Method Item.IsMine(pItem) Rem 'Search for data via relationships SetNew "MemTab.Person.Alias", ($SessionUser) Children "ToDo.List" Children "ToDo.Item" KeepIf "ToDo.Item.Alias", =, `pItem [New] FoundIt | ToDo.Item.Alias EndRem Rem 'Handle not found If Not FoundIt HtmlErr "Sorry, it looks like you have an invalid link." List.All Exit Proc End If EndRem End Method Method Warning.Show(pType) Html "

" HtmlAButton " Cancel", "?" Html "

"
HtmlAlert "Runtime", "&danger", ("Are you sure? Deleting an " & pType & " cannot be undone.") Html "

" HtmlButton " Delete Item" Html "

"
End Method Method List.Redirect [Pull] Req.Path HttpStatus "302" HttpHeader "Location", ("/" & Req.Path) End Method Macro mSetState(pErrFld, pFullFld) If pErrFld : Build (pFullFld & "#"), "Attr", "Attr", ($I), "`[State] Error", "" ElseIf pAnyErr : Build (pFullFld & "#"), "Attr", "Attr", ($I), "`[State] Success", "" End If End Macro

The abstract parent state and child state for viewing ToDoLists and ToDoItems.


.state('app.todos', {
  abstract: true,
  url: '/todos',
  template: '<div ui-view></div>'
})

.state('app.todos.list', {
  url: '',
  template: `
	<div class="container">

    <a ui-sref="app.todos.add" class="m-t-10 m-b-10 btn btn-sm btn-default"><i class="fa fa-plus"></i> New List</a>

    <alert-box theme="default"></alert-box>

    <div class="alert alert-info" ng-if="!todoLists.length">
        <em class="lead">
            <span ng-if="User.isAuthenticated()">You don't have any To Do Lists</span>
            <span ng-if="!User.isAuthenticated()">Login to create a To Do List</span>
        </em>
    </div>

    <div class="panel panel-default" ng-if="todoLists.length" 
        ng-repeat="list in todoLists">
        <div class="panel-heading">
            <h3 class="panel-title">{{list.name}} <span class="pull-right"><b>Due:</b> {{list.dueDate | date:MM:DD:YYYY}}</span></h3>
        </div>
        <div class="panel-body">
            <div class="btn-group" role="group" aria-label="...">
                <div class="btn-group" role="group">
                    <a ui-sref="app.todos.add({ listId: list.id })" class="btn btn-default btn-xs"><i class="fa fa-edit"></i> Edit</a>
                </div>
                <div class="btn-group" role="group">
                    <button type="button" class="btn btn-default btn-xs" ng-click="deleteList(list)"><i class="fa fa-trash"></i> Delete</button>
                </div>
            </div>

            <a ui-sref="app.todos.add-item({ listId: list.id})" class="btn btn-default btn-xs"><i class="fa fa-plus"></i> New Item</a>

            <table class="table table-striped" ng-if="list.toDoItems.length">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Status</th>
                        <th>Due Date</th>
                        <th>Delete</th>
                    </tr>
                </thead>
                <tbody>
                    <tr ng-repeat="item in list.toDoItems">
                        <td><a ui-sref="app.todos.add-item({ listId: list.id,itemId: item.id})">{{item.name}}</a></td>
                        <td>{{item.status}}</td>
                        <td>{{item.dueDate | date: MM/DD/yyyy}}</td>
                        <td>&nbsp;&nbsp;<button type="button" class="btn btn-danger btn-sm" ng-click="deleteItem(list, item)"><i class="fa fa-close"></i></button></td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div><!-- end panel -->

</div>
  `,
  controller: ['$scope', 'AlertService', 'ToDoList', 'todoLists', function ($scope, AlertService, ToDoList, todoLists) {
    // attach resolved ToDoLists & ToDoItems data to template 
    $scope.todoLists = todoLists;

    // delete ToDoList & all ToDoItem relationships
    $scope.deleteList = function (list) {
      AlertService.reset();
      
      // using async to structure/order our chain of promises
      async.series([
        function (seriesCB) {
        // delete ToDoList item relationships first
          ToDoList.toDoItems.destroyAll({
            id: list.id
          })
          .$promise
          .then(function (response) {
            return seriesCB();
          })
          .catch(function (err) {
            return seriesCB(err);
          });
        },
        function (seriesCB) {
          // delete ToDoList second
          ToDoList.destroyById({
            id: list.id
          })
          .$promise
          .then(function (response) {
            return seriesCB();
          })
          .catch(function (err) {
            return seriesCB(err);
          });
        }
      ], function (err) {
        // set an alert message for either error or success 
        if (err) {
          return AlertService.setError({
            show: true,
            title: 'Error deleting List',
            lbErr: err
          });
        }

        AlertService.setSuccess({
          show: true,
          title: list.name + ' deleted successfully'
        });
        
        // remove the ToDoList from the dom
        var index = $scope.todoLists.indexOf(list);
        $scope.todoLists.splice(index, 1);

      });
    };

    // delete a single ToDoItem from a ToDoList
    $scope.deleteItem = function (list, item) {
      AlertService.reset();
      
      // make DELETE request to our api
      ToDoList.toDoItems.destroyById({
        id: list.id,
        fk: item.id
      })
      .$promise
      .then(function (response) {
        // success, remove ToDoItem from ToDoList and show success alert
        var index = list.toDoItems.indexOf(item);
        list.toDoItems.splice(index, 1);
        AlertService.setSuccess({
          show: true,
          title: item.name + ' deleted successfully.'
        });
      })
      .catch(function (err) {
        // fail, show error message
        AlertService.setError({
          show: true,
          title: 'Error deleting List Item',
          lbErr: err
        });
      });
    };

  }],
  resolve: {
    todoLists: ['ToDoList', 'User', function (ToDoList, User) {
      // before displaying page, we require the ToDoLists & ToDoItems data for the current user
      return ToDoList.find({
        filter: {
          where: {
            authorId: User.getCurrentId()
          },
          include: 'toDoItems'
        }
      })
      .$promise
      .then(function (response) {
        return response;
      })
      .catch(function (err) {
        return [];
      });
    }]
  }
})

The state which is responsible for creating new ToDoLists as well as updating existing ToDoLists.


.state('app.todos.add', {
  url: '/add/:listId',
  template: `
    <div class="container">

        <a ui-sref="app.todos.list" class="m-t-10 m-b-10 btn btn-sm btn-default"><i class="fa fa-chevron-left"></i> Back </a>

        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">New To Do List</h3>
            </div>
            <div class="panel-body">

                <alert-box theme="default"></alert-box>

                <form class="form-horizontal" ng-submit="save()">
                    <div class="form-group">
                        <label for="todoName" class="col-sm-2 control-label">Name</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="todoName" placeholder="Name"
                                ng-model="todoList.name">
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="dueDate" class="col-sm-2 control-label">Due Date</label>
                        <div class="col-sm-10">
                            <div class="dropdown">
                                <a class="dropdown-toggle" name="dueDate" id="dueDate" role="button" data-toggle="dropdown" href="#">
                                    <div class="input-group"><input type="text" class="form-control" data-date-time-input="MM-DD-YYYY hh:mm A" data-ng-model="todoList.dueDate"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
                                    </div>
                                </a>
                                <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
                                    <datetimepicker data-ng-model="todoList.dueDate" data-datetimepicker-config="{ dropdownSelector: '#dueDate', startView: 'year' }"/>
                                </ul>
                            </div>
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-sm-offset-2 col-sm-10">
                            <button type="submit" class="btn btn-default"><i class="fa fa-check"></i> Save</button>
                        </div>
                    </div>
                </form>

            </div><!-- body -->
        </div><!-- end panel -->

    </div>
  `,
  controller: ['$scope', '$state', 'AlertService', 'ToDoList', 'todoList', function ($scope, $state, AlertService, ToDoList, todoList) {

    // initialization
    if (todoList) {
      // edit an existing ToDoList
      $scope.todoList = todoList;
    } else {
      // create a new ToDoList
      $scope.todoList = {
        name: '',
        dueDate: new Date(moment().format('MM-DD-YYYY hh:mm A'))
      };
    }
    
    // save a new ToDoList / edit an existing ToDoList
    $scope.save = function () {
      $scope.todoList.authorId = $scope.User.getCurrentId();
      AlertService.reset();
      
      // upsert: create if instance does not exist, otherwise edit
      ToDoList.upsert($scope.todoList)
      .$promise
      .then(function (response) {
        AlertService.setSuccess({
          show: true,
          title: $scope.todoList.name + ' saved successfully.',
          persist: true
        });
        
        // transition to previous state
        $state.transitionTo('app.todos.list');
      })
      .catch(function (err) {
        AlertService.setError({
          show: true,
          lbErr: err,
          title: 'Error Saving'
        });
      });
    };

  }],
  resolve: {
    todoList: ['$stateParams', 'ToDoList', function ($stateParams, ToDoList) {
      // if the URL contains a listId, resolve it from db, else create a new ToDoList
      if (!$stateParams.listId) return null;

      return ToDoList.findById({
        id: $stateParams.listId
      })
      .$promise
      .then(function (res) {
        return res;
      })
      .catch(function (err) {
        return null;
      });
    }]
  }
})

The state which is responsible for creating new ToDoItems as well as updating existing ToDoItems.

 UltraEdit source file - tab-js.js
.state('app.todos.add-item', {
  url: '/add-item/:listId/:itemId',
  template: `
 <div class="container"> <a ui-sref="app.todos.list" class="m-t-10 m-b-10 btn btn-sm btn-default"><i class="fa fa-chevron-left"></i> Back </a> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">New To Do List Item</h3> </div> <div class="panel-body"> <alert-box theme="default"></alert-box> <form class="form-horizontal" ng-submit="save()"> <div class="form-group"> <label for="todoName" class="col-sm-2 control-label">Name</label> <div class="col-sm-10"> <input type="text" class="form-control" id="todoName" placeholder="Name" ng-model="todoListItem.name"> </div> </div> <div class="form-group"> <label for="todoName" class="col-sm-2 control-label">Status</label> <div class="col-sm-10"> <select class="form-control" ng-model="todoListItem.status"> <option value="New">New</option> <option value="In Progress">In Progress</option> <option value="Completed">Completed</option> </select> </div> </div> <div class="form-group"> <label for="dueDate" class="col-sm-2 control-label">Due Date</label> <div class="col-sm-10"> <div class="dropdown"> <a class="dropdown-toggle" id="dueDate" role="button" data-toggle="dropdown" href="#"> <div class="input-group"><input type="text" class="form-control" data-date-time-input="MM-DD-YYYY hh:mm A" data-ng-model="todoListItem.dueDate"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span> </div> </a> <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel"> <datetimepicker data-ng-model="todoListItem.dueDate" data-datetimepicker-config="{ dropdownSelector: '#dueDate', startView: 'year' }"/> </ul> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default" ng-click="saveItem()"><i class="fa fa-check"></i> Save</button> </div> </div> </form> </div><!-- body --> </div><!-- end panel --> </div>
`, controller: '['$scope', '$state', '$stateParams', 'toDoItem', 'AlertService', 'ToDoList', function ($scope, $state, $stateParams, toDoItem, AlertService, ToDoList) { // initalization if (toDoItem) { // update existing $scope.todoListItem = toDoItem; } else { // create new $scope.todoListItem = { name: '', status: 'New', dueDate: new Date(moment().format('MM-DD-YYYY hh:mm A')) }; } // save new item or update existing $scope.saveItem = function () { // attach authorId to generate relationship $scope.todoListItem.authorId = $scope.User.getCurrentId(); AlertService.reset(); ToDoList.toDoItems.create({ id: $stateParams.listId }, $scope.todoListItem) .$promise .then(function (response) { // success alert AlertService.setSuccess({ show: true, title: $scope.todoListItem.name + ' saved successfully.', persist: true }); // transition to prev state $state.transitionTo('app.todos.list'); }) .catch(function (err) { // error alert AlertService.setError({ show: true, title: 'Error saving List Item', lbErr: err }); }); }; }], resolve: { toDoItem: ['$stateParams', 'ToDoItem', function ($stateParams, ToDoItem) { if (!$stateParams.itemId) return null; return ToDoItem.findById({ id: $stateParams.itemId }) .$promise .then(function (res) { return res; }) .catch(function (err) { return null; }); }] } });

This is a highly modular AngularJS Service which I wrote to easily handle any error/success alerts in an easy-to-use manner. This service was designed to be used with LoopBack so that it can easily parse user-friendly error messages from the server's error response.


.factory('AlertService', ['$log', function($log) {
     return {
      show: false,
      persist: false,
        /**
          * setError({ show: true, lbErr: err, title: 'string', ... }) - @params { object } 
          * - Create error with any number of properties and use alert-box
          *   directive to create custom templates
          */
      reset: function () {
        if (this.persist) return this.persist = false;

        this.show = false;
        var $this = this;
        angular.forEach(this, function(value, key) {
          // restricts users from overwriting functions
          if (!angular.isFunction(value)) {
            $this[key] = null;
          }
        });
      },
      showAlert: function () {
        this.show = true;
      },
      hasAlert: function() {
        return this.show ? true : false;
      },
      setError: function(alertObj) {
        // reference this object
        var alert = this;
        this.type = 'error';
        // first attach alert properties to current object
        angular.forEach(alertObj, function(value, key) {
          if (!angular.isFunction(value)) {
            alert[key] = value;
          }
        });
        // process a loopback err
        if (alert.lbErr) {
          $log.debug('lbErr: ', alert.lbErr);
          // check err obj for user friendly msgs
          if (alert.lbErr.data 
              && alert.lbErr.data.error 
              && alert.lbErr.data.error.details 
              && alert.lbErr.data.error.details.messages) {
            var errMsgs = alert.lbErr.data.error.details.messages;
            var errors = [];
            angular.forEach(errMsgs, function(errArr) {
              angular.forEach(errArr, function(errMsg) {
                errors.push(errMsg);
              });
            });
            // set user friendly loopback errors
            alert.errors = errors;
          }
          // check err obj for error title
          if (alert.lbErr.data 
              && alert.lbErr.data.error 
              && alert.lbErr.data.error.message) {
            var errTitle = alert.lbErr.data.error.message;
            // set user friendly error title
            alert.title = errTitle;
          }
          // overwrite title with passed in 1
          if (alertObj.title) {
            alert.title = alertObj.title;
          }
        }
      },
      setSuccess: function(alertObj) {
        // reference this object
        var alert = this;
        this.type = 'success';
        // first attach alert properties to current object
        angular.forEach(alertObj, function(value, key) {
          if (!angular.isFunction(value)) {
            alert[key] = value;
          }
        });
      }
       
    };
  }])
  
  .directive('alertBox', ['AlertService', function(AlertService) {
    return {
      restrict: 'E',
      templateUrl: function(scope, elem) {
        // Use default theme if no theme is provided
        if (elem.theme) {
          return 'js/directives/alert-box/' + elem.theme + '.html'
        } else {
          return 'js/directives/alert-box/default.html'
        }
      },
      link: function(scope, elem, attrs, ctrl) {
        // attach AlertService to alert-box's scope
        scope.alert = AlertService;
      }
    };
  }]);
  
  // 'js/directives/alert-box/default.html'
<div class="panel" ng-class="{'panel-danger': alert.type === 'error', 'panel-success': alert.type === 'success' }" 
     ng-if="alert.show" role="alert">
  <div class="panel-heading">
    <button type="button" class="close" aria-label="Close" ng-click="alert.show = false">
      <span aria-hidden="true">&times;</span>
    </button>
    <h3 class="panel-title" ng-if="alert.title">
      <span>
        <i ng-if="alert.type === 'error'" class="fa fa-exclamation"></i>
        <i ng-if="alert.type === 'success'" class="fa fa-check"></i>&nbsp;{{alert.title}}
      </span>
    </h3>
  </div>
  <div class="panel-body" ng-if="alert.errors">
    <ul>
      <li ng-repeat="error in alert.errors">{{error}}</li>
    </ul>
  </div>
</div>

Model declaration for ToDoLists. ACLs are used to enforce security restrictions.

 UltraEdit source file - tab-js.js
{
  "name": "ToDoList",
  "base": "PersistedModel",
  "strict": true,
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "name": {
      "type": "string",
      "required": true
    },
    "dueDate": {
      "type": "date"
    }
  },
  "validations": [],
  "relations": {
    "author": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": "authorId"
    },
    "toDoItems": {
      "type": "hasMany",
      "model": "ToDoItem",
      "foreignKey": "toDoListId"
    }
  },
  "acls": [
    {
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "DENY"
    },
    {
      "accessType": "CREATE",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW"
    },
    {
      "accessType": "READ",
      "principalType": "ROLE",
      "principalId": "$owner",
      "permission": "ALLOW"
    },
    {
      "accessType": "UPDATE",
      "permission": "ALLOW",
      "principleType": "ROLE",
      "principleId": "$owner"
    },
    {
      "accessType": "DELETE",
      "permission": "ALLOW",
      "principleType": "ROLE",
      "principleId": "$owner"
    }
  ],
  "methods": {}
}

Model declaration for ToDoItems. ACLs are used to enforce security restrictions.

 UltraEdit source file - tab-js.js
{
  "name": "ToDoItem",
  "base": "PersistedModel",
  "strict": true,
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "name": {
      "type": "string",
      "required": true
    },
    "dueDate": {
      "type": "date"
    },
    "status": {
      "type": "string"
    }
  },
  "validations": [],
  "relations": {
    "toDoList": {
      "type": "belongsTo",
      "model": "ToDoList",
      "foreignKey": ""
    }
  },
  "acls": [
    {
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "DENY"
    },
    {
      "accessType": "CREATE",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW"
    },
    {
      "accessType": "READ",
      "principalType": "ROLE",
      "principalId": "$owner",
      "permission": "ALLOW"
    },
    {
      "accessType": "UPDATE",
      "permission": "ALLOW",
      "principleType": "ROLE",
      "principleId": "$owner"
    },
    {
      "accessType": "DELETE",
      "permission": "ALLOW",
      "principleType": "ROLE",
      "principleId": "$owner"
    }
  ],
  "methods": {}
}