2

I just started using Bacon.js and it's truly awesome. Although sometimes I do struggle to find the right way of doing things. For example I want to have an angular directive with a draggable part. I shamelessly pick into someone's jsBin and tried to adapt that code for angular

I am trying to make a table with resizable columns. So if I do something like this in column-header directive

link: (scope, element, attrs)->
    el = element.find('.column-separator')
    doc = $(document)
    mMove = doc.asEventStream('mousemove')
    startDrag = el.asEventStream('mousedown')
    endDrag = doc.asEventStream('mouseup').takeWhile mMove

    # in this case unlike the example in jsBin I don't care about vertical axis, 
    # only horizontal "X"
    getDelta = (t)-> a = t[1]; b = t[0]; return a-b   

    add = (p1,p2)-> p1 + p2

    draggingDeltas = startDrag.flatMap ->
        return mMove
                .map '.clientX'
                .slidingWindow 2,2
                .map getDelta
                .takeUntil endDrag

    pos = draggingDeltas.scan 0, add
    pos.onValue (pos)-> el.css left: pos+"px"

This kinda works, but now this directive will register 'mousemove' and 'mouseup' events all over the page. I probably can add some takeWhile statements, and the matter of fact I just tried and it didn't really work.

I mean what's the pattern of using global event handler's like $(document).asEventStream('click') in angular app?

  • You can create handlers in a directive and then use takeWhile, takeUntil but then that will work only once, since the stream eventually stops. Do you have to re-initialize the stream every time you need to respond to a document.click? Also isn't it a bad thing to have "document" level events in bunch of places? If you write in a directive $(document).asEventStream('mouseup') and use that directive two hundred times, wouldn't that create actual two hundred listeners?

  • Or you gotta introduce these sort of stream variables globally for entire app to use, and then inside a directive do map, filter and reduce? But then what if someone calls takeUntil and stream stops flowing completely and can't be used in other parts of the app?

  • Or maybe listen on the top level of the app and emit $rootScope event for every value in the stream and then in a directive or view use ng-bacon's $rootScope.$asEventStream(event)? Wouldn't that make the app somewhat less responsive? Say if you need to respond on 'keydown' and 'keyup' events?

Can someone show me an example how FRP can be used in angular directives (particularly drag-N-drop sample would be appreciated)

iLemming
  • 34,477
  • 60
  • 195
  • 309

1 Answers1

2

I'm not sure how exactly this fits into the angular philosophy, but I would definitely just add those handlers once.

In a global singleton class:

var mouseHandler = {
  up: $(document).asEventStream('mouseup'),
  move: $(document).asEventStream('mousemove')  
} 

The inside a single component, you add handlers to those but make sure you always use takeUntil, that way bacon only handles those events if there is an actual need.

function dragHandler(element) {
  var start = $(element).asEventStream('mousedown')
  var delta = start.flatMap(function() {
    return mouseHandler.move
      .map('.clientX')
      .slidingWindow(2,2)
      .map(getDelta)
      .takeUntil(mouseHandler.up)
  })

  var pos = delta.scan(0, add)
  pos.onValue(function(p) {
    $(element).css({left: p + "px"})
  })

  function getDelta(t) { return t[1]-t[0] }
  function add(a,b) { return a+b }
}

http://jsbin.com/yekojitake/3/edit

OlliM
  • 7,023
  • 1
  • 36
  • 47
  • so here's the problem with using `takeWhile` and `takeUntil`. Let me describe it. Let's say I have a directive, when hovered over it opens a div, and then when user clicks anywhere (document.click) it closes the div. Both operations managed by `toEventStream` streams. I can use `takeUntil` for the later but then it stops the stream completely, how do I re-register the stream? When I tried to tie one `toEventStream` from inside other's `onValue` it didn't work. – iLemming Feb 06 '15 at 20:48
  • also I read somewhere that Bacon uses singletones anyway. So registering document level events in a bunch of places is fine. Still need to use them wisely (with `takeWhile` and `takeUntil`) – iLemming Feb 06 '15 at 20:51
  • One good way to deregister is to keep a "close" stream. That stream gets a value when the module / directive / whatever part of code is closing / being destroyed. The you add `takeUntil(close)`to all streams you use, in this case it's enough to add it to start. – OlliM Feb 07 '15 at 19:00
  • When there are no listeners, bacon does nothing, so if all users of `mouseHandler` are destroyed, bacon does nothing with the listener. – OlliM Feb 07 '15 at 19:01