Decoupled Directives

Another talk about directives?

Sorry about that.

(But they are the best part of Angular.)

.directive('myDirective', function() {
  return {
    // ...
    scope: {
      'value': '=ngModel',
      'minValue': '@',
      'slideStart': '&'
    },
    //...
  }
}

What the fudge does it mean?

Huh? Scope


Is related, but not the same as $scope.


Example!

Let's make a slider UI widget.

Start with some boilerplate (Coffeescript from now)


.directive 'slider', () ->
  template: """
    <div class="slider">
      <div class="nib"></div>
    </div>
  """
  scope:
    value: '=ngModel'
  replace: true
  restrict: 'E'
  link: ($scope, $element, $attrs) ->
    return
    

Usage in view

<slider ng-model="digits"></slider>

Bidirectional binding:
$scope.value in directive <=> digits in view

Happens due to { value: '=ngModel' }

Make it reflect a value


<div class="slider">
  <div class="nib"
       ng-style="nibStyle()">
  </div>
</div>

link: ($scope, $element, $attrs) ->
  $scope.nibStyle = () ->
    value = parseFloatDefault $scope.value, 0
    
    proportion = clamp(value, 0, 1)
    return {
        marginLeft: "#{100 * proportion}%"
    }
    

Make it move


<div class="slider">
  <div class="nib" ng-style="nibStyle()"
       ng-mousedown="startSliding($event)">
  </div>
</div>

$scope.startSliding = (event) ->
  sliding = true
  
angular.element(document)
.bind 'mousemove', (event) ->
  return if not sliding
  
  # ... calculate new value ...
  
  $scope.$apply () ->
      $scope.value = newValue
      
.bind 'mouseup', () ->
    sliding = false
    

The cool bit: adding options


scope:
  value: '=ngModel'
  minValue: '@'
  maxValue: '@'

<slider ng-model="digits"
        min-value="0"
        max-value="100">
</slider>

Sets $scope.minValue to min-value attribute

The options can be expressions!


<slider ng-model="digits"
        min-value="{{min}}"
        max-value="{{max}}">
</slider>

<input type="text" ng-model="max"/>
<input type="text" ng-model="min"/>

So what?


Yo Dawg.


<slider ng-model="digits"
        min-value="{{min}}"
        max-value="{{max}}">
</slider>
<slider ng-model="min"
        min-value="-100"
        max-value="{{max}}">
</slider>
<slider ng-model="max"
        min-value="{{min}}"
        max-value="100">
</slider>

& to finish off

Get it?

Publish a function to scope.

scope:
  # ...
  slideStart: '&'
  slideStop: '&'

<slider ng-model="digits"
        slide-start="isSliding = true"
        slide-stop="isSliding = false">
</slider>

$scope.startSliding = (event) ->
  # ...
  $scope.slideStart()
  
$document.bind 'mouseup', () ->
  # ...
  $scope.$apply () ->
    $scope.slideStop()
    

And in the view...


<img src="slide.gif" ng-show="isSliding" />

You can also get a value out of the function bound with &

<slider ng-model="digits"
        min-value="{{min}}"
        max-value="{{max}}"
        scale-value="log(value)"
        scale-proportion="exp(proportion)">
</slider>

newValue = $scope.scaleProportion({
  proportion: newProportion
})
# ...
newProportion = $scope.scaleValue({
  value: newValue
})

Recap: the main parts


Scope is like a function signature


scope:
  value: '=ngModel'
  minValue: '@'
  maxValue: '@'
  slideStart: '&'
  slideStop: '&'

<slider ng-model="digits"
        min-value="{{min}}"
        max-value="{{max}}"
        slide-start="isSliding = true"
        slide-stop="isSliding = false">
</slider>            

Questions?