zajca
zajca

Reputation: 2418

Angular directive implement prototype

I'm trying to implement my game in canvas to angular directive, but I can't figure out how to implement my class "prototype"

my code is in coffeeScript but javascript is not a problem :)

my directive:

angular.module("tetris", []).directive("tetris", ($timeout) ->
 restrict: "E"
 transclude: true
 scope: {}
 link: ($scope, $element, $log) ->
  game = new Game($element.children()[0],$element.children()[1],$element.children()[2])
  game.initGame()
   document.onkeydown=(e)->
    game.keyDown(e);
   document.onkeyup=(e)->
    game.keyUp(e);
  return
 template:"<div class=\"row relative\" id=\"canvas\">"+
 "<canvas class=\"absolute\" width=\"120\" height=\"250\" style=\"z-index:1\">"+
 "Your browser is not supporting canvas"+
 "</canvas>"+
 "<canvas class=\"absolute\" width=\"120\" height=\"250\" style=\"z-index:2\"></canvas>"+
 "<canvas class=\"absolute\" width=\"120\" height=\"250\" style=\"z-index:3\"></canvas>"+
 "</div>"
 replace: true
)

and this is my eventHandler class which i want to implement to directive to just promote variables and events to scope. This class is called from Game class.

basically I want promote variables from Game prototype to parent scope of directive

class EventHandler
 constructor:->
  @scoreElm = document.getElementById('score')
  @levelElm = document.getElementById('level')
  @bombElm = document.getElementById('bomb')
  @nextElm = document.getElementById('next')

 updateScore:(score)->
  @scoreElm.innerHTML=score

 setBombCountDown:(time)->
  @bombElm.style.display.block
  @bombElm.innerHTML=time

 hideBombCountDown:->
  @bombElm.style.display.none

//EDIT:

I figured out how to do it, but I don't feel that this is a right way. Do you have any suggestion how to do it better?

angular.module("tetris", []).directive("tetris", ($timeout) ->
  restrict: "E"
  transclude: true
  scope: false
  link: ($scope, $element, $log) ->
    class EventHandler
      updateScore:(score)->
        $scope.score = score
        $scope.$apply()

      setBombCountDown:(time)->
        $scope.bombTime=time
        $scope.$apply()

      hideBombCountDown:->
        $scope.bombTime=null
        $scope.$apply()

    game = new Game($element.children()[0],$element.children()[1],$element.children()[2],EventHandler)
    game.initGame()
    document.onkeydown=(e)->
      game.keyDown(e);
    document.onkeyup=(e)->
      game.keyUp(e);
    return

  template:"<div class=\"row relative\" id=\"canvas\">"+
  "<canvas class=\"absolute\" width=\"120\" height=\"250\" style=\"z-index:1\">"+
  "Your browser is not supporting canvas"+
  "</canvas>"+
  "<canvas class=\"absolute\" width=\"120\" height=\"250\" style=\"z-index:2\"></canvas>"+
  "<canvas class=\"absolute\" width=\"120\" height=\"250\" style=\"z-index:3\"></canvas>"+
  "</div>"
  replace: true
)

Upvotes: 1

Views: 743

Answers (1)

oztune
oztune

Reputation: 325

Right now your directive is manipulating its parent scope by adding arbitrary members to it directly, which creates a sort of implicit interface between your directive and the outside world. This approach is prone to errors and is hard to maintain.

The solution

When building reusable directives it's common to use an isolate scope that exposes its members via attributes on the directive's element. This way the parent scope can decide which of its attributes to bind with your directive's members.

As an example to illustrate this, here's how you could restructure your tetris directive:

.directive('tetris', function ($timeout) {
    return {
        restrict: 'E',
        scope: {
            score: '=',
            bombTime: '='
        },
        link: function (scope, el) {
            // Same code as in your example
        }
    };
};

The major difference here is that the scope is isolate (meaning our directive has no way to "pollute" the parent scope) and we have the score and bombTime attributes bound to attributes on our directive's dom element, which the parent scope can access safely.

Note - There doesn't seem to be a reason for using transclude: true, since your directive doesn't incorporate the contents given to it by the parent view into its behavior in any way.

Using the directive now looks like this:

<tetris score="myScore" bomb-time="myBombTime"></tetris>
<span>Score: {{myScore}}</span>
<span>Time: {{myBombTime}}</span>

Now the user of your directive can choose which scope members to associate with the game's score and bombTime attributes (myScore and myBombTime in this example to illustrate that they belong to the parent scope).

Conclusion

While this approach to writing directives may seem too verbose at first, it's important if you ever plan to change the structure of the page your directive lives in, place your directive into a page which has already been set up with different scope members for keeping score and time, or if you plan to write unit tests for your code.

Upvotes: 1

Related Questions