Data Binding Best Practices

Brendan McLoughlin

Web Unleashed 2013

https://bmac.github.io/data-binding-best-practices

Bob

Bocoup

@Bocoup

I Love Data Binding

  • Great for applications that have complex UI requirements
  • Enterprise apps (Validation, Workflows)
  • Dashboards (Real-time)
  • WebRTC (Heavy use of browser APIs)
  • Complex DOM manipulation in response to data changes


https://lh3.ggpht.com/-7ntFFNpikqM/Tx67iYPZTpI/AAAAAAAABaQ/lk1n55zhFls/s1600/i_love_you_cupcake.jpg

Data Binding is Not a New Concept

  • Microsoft's Windows Presentation Foundation
  • Adobe's Flex
  • Apple's Key Value Observation
  • PowerBuilder's DataWindow
  • Others

JavaScript's Data Binding Landscape

  • Angular (late 2009)
  • Knockout (2010)
  • Ember (2011)
    • Sproutcore (2007)
  • Meteor (2011)
  • Ractive (2012)
  • Polymer (2012)

Presentation Model

Source of Truth for Application State

  • $scope
  • Controller
  • View Model

Template

Describes the Relationship Between State and the DOM

  • HTML
  • Handlebars
  • HTML

Angular

function Controller($scope) { $scope.name = '{{name}}'; $scope.length = function() { return $scope.name.length; }; // this.length(); => {{ length() }} $scope.reset = function() { $scope.name = 'foo'; }; };
<input ng-model="name" value="{{name}}"/> <button ng-click="reset()">Reset</button> <div>Name: {{name}} Length: {{length()}} </div>

Name: {{name}}     Length: {{length()}}

Angular

$scope.price = {{price}}; //... myModule.directive('numberInput', function () { return { template: '<input type="text"/>', restrict: 'E', require: 'ngModel', link: function($scope, element, attrs, modelCtrl) { element.on('blur', function() { $scope.$apply(function() { var num = parseInt(element.value.replace(/,/g, ''), 10); modelCtrl.$modelValue = num; }); }); modelCtrl.$render = function () { element.value = modelCtrl.$modelValue.toLocaleString(); }; // missing closing braces...
<label> Price: <number-input ng-model="price"></number-input></label>

Knockout

var ViewModel = function() { this.name = ko.observable(''); this.length = ko.computed(function() { return this.name().length; }, this); // this.length(); => this.reset = function() { this.name('foo'); }; };
<input data-bind="value: name, valueUpdate: 'afterkeydown'" value=""/> <button data-bind="click: reset">Reset</button> <div>Name: <span data-bind="text: name"></span> Length: <span data-bind="text: length"></span></div>

Name:     Length:

Hints that your Code Belongs in a Service

  • Ajax
  • Transformation of Data
  • Iteration or Underscore Methods
  • Managing Promises
  • Facade for 3rd party APIs

Logicless Templates

http://www.calwatchdog.com/wp-content/uploads/2013/05/Spock-logic

Keep Presentation Logic Close to the DOM

<label>Order Date: {{purchaseOrder.createdAt | date:'MM/dd/yyyy'}}</label>
<label>Order Date: {{date purchaseOrder.createdAt 'MM/DD/YYYY'}}</label>
<label>Order Date: <span data-bind="text: fmt.date(purchaseOrder.createdAt, 'MM/DD/YYYY')"></span> </label>
  • Angular Filters
  • Handlebars Helpers
  • functions

Binding

Update the DOM to Reflect the Presentation Model

Translates DOM Events into Application Actions

  • Directives
  • Components
  • Binding

Knockout

viewModel.price = ko.observable(); ko.bindingHandlers.number = { init: function(element, valueAccessor) { var observable = valueAccessor(); element.addEventListener('blur', function() { var number = parseInt(element.value.replace(/,/g, ''), 10); observable(number); }); }, update: function(element, valueAccessor) { var number = ko.unwrap(valueAccessor()); element.value = number.toLocaleString(); }};
<label> Price: <input type="text" data-bind="number: price" /></label>

Expose State as an Observable

var VM = function() { this.name = ko.observable(''); this.inputHasFocus = ko.observable(); this.highlight = ko.computed(function () { return !(this.name() || this.inputHasFocus()); // }, this); }; ko.bindingHandlers['hasFocus'] = { init: function (element, valueAccessor) { var observable = valueAccessor(); $(element).on('focus', function () { observable(true); }); $(element).on('blur', function () { observable(false); }); // ...
<label data-bind="css: {'hot-pink': highlight}">Favorite Fruit:</label> <input id="fruit" data-bind="value: name, hasFocus: inputHasFocus"/>