How to model Snap.svg map in AngularJS?
angularjs, coffeescript, javascript, snap.svg, svg
Solution
You should consider writing a directive. Directive are intended to encapsulate reusable components, especially graphic ones.
define [
'app' # or use any other way to get to your Angular module.
'snap'
], (app, Snap) ->
app.directive 'awesomeMap', ->
# directive template: will be injected into controller ($element variable)
template: "<svg></svg>"
# will remplace hosting element
replace: true
# applicable as element and attribute
restrict: 'EA'
# here the "parameters" of your directive: can be parsed from HTML, or bound to parent scope (my example).
scope:
svgPath: '=?path'
# controller
controller: AwesomeMap
class AwesomeMap
# Controller dependencies: scope and HTML element
@$inject: ['$scope', '$element']
# Controller constructor: bind methods and attributes to current scope
#
# @param scope [Object] directive scope
# @param element [Object] root element's HTML (angular.element)
constructor: (@scope, element) -
@snap = Snap(element[0])
Snap.load(@scope.svgPath, (data) ->
console.log "Loaded map in Map!">
# do your stuff here !
Your directive needs to be loaded after Angular's module initialization (don't forget to require it somewhere), and you just have to use it in your HTML.
<div data-ng-controller="AParentScope">
My awesome map <awsome-map data-path="scopeAttribute"></awsome-map>
(Using CoffeeScript classes with Angular is a challenge, I'm happy to find someone else trying :))
Problem
I am making a web interface for a board game online. I want to load a Snap.svg map with Snap.load, ie asynchronously. After it loads, I want to add a watch to a property of the scope, and add some colour to the map according to the property (which is an object). My code is essentially this: ``` .controller('GameCtrl', [ '$scope' '$routeParams' 'GameService' ( $scope $routeParams GameService ) -> $scope.game = GameService.get($routeParams.gameId) $scope.map = Snap("#map") Snap.load("img/classical.svg", (data) -> console.log "Loaded map!" data.select("#provinces").attr style: "" provinces = data.selectAll("#provinces path") for province in provinces province.attr style: "" fill: "#FFFFFF" "fill-opacity": "0" $scope.map.append(data) deregisterWatch = $scope.$watch('game', -> console.debug "Game loaded!", $scope.game.data.Id for provinceName,unit of $scope.game.data.Phase.Units provinceName = provinceName.replace '/', '-' province = $scope.map.select("##{provinceName}") province.attr style: "" fill: powers[unit.Nation].colour "fill-opacity": "0.8" deregisterWatch() ) ) ]) ``` Now, the problem is that I want to move the map to its own "class" or file, but it has to know about the `$scope` somehow, to be able to set the watch after loading. My first, naïve approach, is to extract the map and just pass in the scope: ``` define([ 'snap' ], ( Snap ) -> 'use strict' Map = ($scope, selector, svgPath) -> that = {} that.provinces = {} ... that.snap = Snap(selector) Snap.load(svgPath, (data) -> console.log "Loaded map in Map!" data.select("#provinces").attr style: "" provinces = data.selectAll("#provinces path") for province in provinces that.provinces[province.attr("id")] = province province.attr style: "" fill: "#FFFFFF" "fill-opacity": "0" that.snap.append(data) deregisterWatch = $scope.$watch('game', -> console.debug "Game loaded!", $scope.game.data.Id for provinceName,unit of $scope.game.data.Phase.Units provinceName = provinceName.replace '/', '-' that.colourProvince(provinceName, that.powerColors[unit.Nation].colour) deregisterWatch() ) ) that.colourProvince = (abbr, colour) -> ... return that return Map ) ``` But I think there must be a more angular way to do this. Should I perhaps make a directive? Any other suggestion?