I started creating a little remote control App using Angular. I created a joystick directive which is using the canvas.
I wrapped it in an angular directive and it works quite nice. You can see an example here (NOTE: Only touch, no mouse events yet):
What i want to do is control a car which basicall means i will keep the joystick dragged all the time and additionally i want to control another button e.g. for the horn. I would prefer using a simple button here.
But all the handlers like ng-click stop working if i drag the joystick around. I can listen to other touch events (e.g. simply add another joystick, works independently) on the canvas and of course other canvas, but can i simply listen to other events like touch somehow? How do i achieve this the most angular / simplest way?
Here the code from the fiddle:
angular.module('myApp').directive('joystick', function() {
    function joystickController ($scope) {
    }
    return {
        restrict : 'E',
        controller : ['$scope', function ($scope) {
            return joystickController($scope);
        }],
        scope : {
            // Using primitives here did not work, so we use an Object, see: http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs
            position : '='
        },
        template : '<canvas class="joystickCanvas"></canvas>',
        link : function(scope, element) {
            var joystickHeight = 200;
            var joystickWidth  = 200;
            var center = {
                x : joystickHeight / 2,
                y : joystickWidth / 2
            };
            var radiusCircle = 35;
            var radiusBound = 50;
            // Canvas and context element
            var container = element[0];
            var canvas = container.children[0];
            var ctx = canvas.getContext('2d');
            // Id of the touch on the cursor
            var cursorTouchId = -1;
            var cursorTouch = {
                x : center.x,
                y : center.y
            };
            function resetCanvas() {
                canvas.height = joystickHeight;
                canvas.width = joystickWidth;
            }
            function onTouchStart(event) {
                var touch = event.targetTouches[0];
                cursorTouchId = touch.identifier;
                cursorTouch = {
                    x : touch.pageX - touch.target.offsetLeft,
                    y : touch.pageY - touch.target.offsetTop
                };
            }
            function onTouchMove(event) {
                // Prevent the browser from doing its default thing (scroll, zoom)
                event.preventDefault();
                for(var i = 0; i < event.changedTouches.length; i++){
                    var touch = event.changedTouches[i];
                    if(cursorTouchId === touch.identifier)
                    {
                        cursorTouch = {
                            x : touch.pageX - touch.target.offsetLeft,
                            y : touch.pageY - touch.target.offsetTop
                        };
                        var scaleX = radiusBound / (cursorTouch.x - center.x);
                        var scaleY = radiusBound / (cursorTouch.y - center.y);
                        if(Math.abs(scaleX) < 1) {
                            cursorTouch.x = Math.abs(cursorTouch.x - center.x) * scaleX + center.x;
                        }
                        if (Math.abs(scaleY) < 1) {
                            cursorTouch.y = Math.abs(cursorTouch.y - center.y) * scaleY + center.y;
                        }
                        scope.$apply(
                            scope.position = {
                                x : Math.round(((cursorTouch.x - center.x)/radiusBound) * 100),
                                y : Math.round(((cursorTouch.y - center.y)/radiusBound) * -100)
                            }
                        );
                        break;
                    }
                }
            }
            function onTouchEnd() {
                cursorTouchId = -1;
                scope.$apply(
                    scope.position = {
                        x : 0,
                        y : 0
                    }
                );
                cursorTouch.x = center.x;
                cursorTouch.y = center.y;
            }
            function draw() {
                // Clear the canvas
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.beginPath();
                ctx.strokeStyle = 'cyan';
                ctx.lineWidth = 5;
                ctx.arc(center.x, center.y, radiusCircle, 0, Math.PI*2, true);
                ctx.stroke();
                ctx.beginPath();
                ctx.strokeStyle = 'cyan';
                ctx.lineWidth = 2;
                ctx.arc(center.x, center.y, radiusBound, 0, Math.PI*2, true);
                ctx.stroke();
                ctx.beginPath();
                ctx.strokeStyle = 'cyan';
                ctx.lineWidth = 2;
                ctx.arc(cursorTouch.x, cursorTouch.y, radiusCircle, 0, Math.PI*2, true);
                ctx.stroke();
                requestAnimFrame(draw);
            }
            // Check if touch is enabled
            var touchable = true;
            if(touchable) {
                canvas.addEventListener( 'touchstart', onTouchStart, false );
                canvas.addEventListener( 'touchmove', onTouchMove, false );
                canvas.addEventListener( 'touchend', onTouchEnd, false );
                window.onorientationchange = resetCanvas;
                window.onresize = resetCanvas;
            }
            // Bind to the values from outside as well
            scope.$watch('position', function(newval) {
                cursorTouch = {
                    x : ((newval.x * radiusBound) / 100) + center.x,
                    y : ((newval.y * radiusBound) / -100) + center.y
                };
            });
            resetCanvas();
            draw();
        }
    };
});
