UI Architecture & Lessons Learned

Welcome to the web

MagicBullet.js?

The web is hostile.

UI programming is hard.

AngularJS

It compiles your DOM

& asks you to use your markup as a DSL

  • ui-dropdown
  • ui-tile
  • ui-selectable
  • case-card
  • case-properties
  • alarm-card 
  • widget
  • ng-if
  • ng-class
  • ng-include
  • ng-show
  • ng-controller
  • input 
  • form

It sets up "scopes" (view models)

<html ng-app="ToDo">
    <div ng-controller="ToDoCtrl">
        <ul>
            <li ng-repeat="item in todos">{{item.text}}
                <small>({{$index}} of {{todos.length}})</small>
            </li>
        </ul>
        <form ng-submit="addTodo()">
            <input type="text" ng-model="newTodo"/>
        </form>
    </div>
</html>
angular.module('ToDo')

.controller 'ToDoCtrl', ($scope) ->
    $scope.newToDo = ''

    $scope.items = [
        {id:0, text : 'Get groceries'}
        {id:1, text : 'Pick up kids'}
    ]

    $scope.addToDO = ->
        $scope.items.push {id : $scope.items.length, text : $scope.newToDo}
        $scope.newToDo = ''

About scopes though...

they are tied to the structure of your dom

<html ng-app>
    <div ng-controller="ToDoCtrl">
        <ul>
            <li ng-repeat="item in todos">{{item.text}}
                <small>({{$index}} of {{todos.length}})</small>
            </li>
        </ul>
        <form ng-submit="addTodo">
            <input type="text" ng-model="newTodo"/>
        </form>
    </div>
</html>

It gives you change detection

$scope.$watch "themeName", (newTheme, prevTheme)->
    if newTheme != prevTheme
      htmlTag
        .removeClass(prevTheme)
        .addClass(newTheme)
      $scope.setThemeName(newTheme)
<span ng-show="!widget.settingsOpen">
   <span>
     {{widget.name}}
   </span>
   <small ng-class="{redTimeScale: widget.settings.timeScale.showWarning}">
     {{widget.settings.timeScale.get()}}
   </small>
</span>
$scope.$on 'nubChange', (event, nub, index, nubsID) -> #...
$scope.$on 'dragResizeEnd', (e, elem, width, resizeID) -> #...
$scope.$on 'filtered-select-up', (e, data) -> #...
$scope.$on 'filtered-select-selected', (event, data) -> #...
$rootScope.$on 'DRILL-DOWN', -> #...
<div id="CaseDetails" ng-controller="CaseDetailsController">
    Case Details
    <div case-card></div>
    <div case-evidence></div>
</div>
class CaseUI

    showCaseCard : () ->
        caseToShow = this.parent.parent.case
        # ...
class CaseUI

    showCaseCard : (caseToShow) ->
        # ...

Uhhhhhhh....

  • MV*?
  • Angular watches scale poorly, but...
  • Other eventing mechanisms can make spaghetti!
  • Scope inheritance creates implicit data contracts
  • We are struggling with separation of concerns

In summary...

Lines of separation

  • Stop treating the scope as your model

Death to scopes!

  • Isolate representation of state into models
  • Interactions affect the model
  • Views watch the model

Make your own models

  • Treat your directives like an api
  • Isolate scopes, require explicit input

Death to scope inheritance!

<case-card case-id="123"></case-card>

<ui-date-picker ng-model="ctrl.dateModel" format="MM/DD/YY"></ui-date-picker>

<ui-dropdown
  ui-dropdown-keys="{display: 'name', selected: 'name' }"
  ng-model="example.selected"
></ui-dropdown>
  • Inheritance is hard to trace
  • Prefer composition with services

Death to inheritance!

angular.module('Analyze').controller 'AnalyzeCtrl', (
  $scope, $window, Socket, $location, $rootScope, $timeout, $filter,
  MasterQuery, EVENT_INDEX_ID, Layout, Task, User,
  CurrentView, VolumeChart, Drilldown, AuditLogModel, AuditLogSvc) ->


    # ...
  • Watches & digest are responsible for the view
  • Use observability of models for business logic

Death to watches!

Putting it together...

Model

Controller

View

Services

Drawing time!

Made with Slides.com