Вопрос

I've created a custom directive for rendering tags such as <rect> and <circle> into <svg> elements.

I have a shapes model which is just an array of objects with various attributes such as width, height and stroke-width.

These shapes get rendered into an SVG by my custom directive

<svg height="400" width="600">
  <dr-shape ng-repeat="shape in shapes"></dr-shape>
</svg>

The directive definition looks like this (heavily commented):

app.directive('drShape', ['$compile', function($compile) {
  // A helper function for adding attributes to an element.
  var bindNgAttr = function(el, attribute, value) {
    el.attr('ng-attr-' + attribute, '{{shape.' + attribute + '}}');
  };

  return {
    restrict: 'E',
    link: function(scope, element, attrs) {

      // #makeNode simply creates a DOM node using `document.createElementNS`
      // so that it can be appended to an SVG element. It also assigns
      // any attributes on the directive.
      var shape = makeNode(scope.shape.tagName, element, attrs);
      var elementShape = angular.element(shape);

      // This part iterates through a shape's attributes and attaches them
      // to the created node. I'm doing this (rather than declaring them
      // where the directive is used in the template because <circle> nodes
      // take different attributes to <rect> nodes. Thus I can't hardcode them.
      for (var attribute in scope.shape) {
        // #isPublic simply ensures that we don't assign any '$' attributes.
        if (isPublic(attribute, scope.shape[attribute])) {
          // Here we use the helper defined above.
          bindNgAttr(elementShape, attribute);
        }
      }

      // Here I'm adding a click listener to the element. This works and
      // when I click a <rect> I can see the console log. The fill color
      // never seems to change in the browser however. I can only assume that
      // the model data is being changed but the binding with the DOM has been
      // broken.
      elementShape.on('click', function() {
        console.log('Clicked in directive');
        scope.shape.fill = '#bada55';
      });

      element.replaceWith(shape);

      // Not sure what this does. It comes from: http://goo.gl/ZoYpQv
      attrs.$observe('value', function(value) {
        scope['value'] = parseInt(value, 10);
        $compile(shape)(scope);
      });
    }
  };
}]);

When I run this with some shape data, I get the following SVG in the browser:

<svg height="400" width="600">
  <!-- ngRepeat: shape in shapes -->
  <rect ngRepeat="shape in shapes" strokeWidth="3" ng-attr-stroke="{{shape.stroke}}" ng-attr-fill="{{shape.fill}}" ng-attr-x="{{shape.x}}" ng-attr-y="{{shape.y}}" ng-attr-width="{{shape.width}}" ng-attr-height="{{shape.height}}" ng-attr-tagname="{{shape.tagName}}" stroke="#aea086" fill="#fff" x="239" y="89" width="25" height="22" tagname="rect"></rect>
  <!-- end ngRepeat: shape in shapes -->
  <rect ngRepeat="shape in shapes" strokeWidth="3" ng-attr-stroke="{{shape.stroke}}" ng-attr-fill="{{shape.fill}}" ng-attr-x="{{shape.x}}" ng-attr-y="{{shape.y}}" ng-attr-width="{{shape.width}}" ng-attr-height="{{shape.height}}" ng-attr-tagname="{{shape.tagName}}" stroke="#a265e7" fill="#fff" x="233" y="6" width="12" height="43" tagname="rect"></rect>
  <!-- end ngRepeat: shape in shapes -->
</svg>

Here's a JSFiddle with the above code.

As I explain in the comments in the code block above, manipulating the data in any shapes model doesn't update the corresponding SVG DOM node.

I've tried calling scope.$apply() and scope.$digest() in the click handler but it doesn't change anything. (EDIT: This is incorrect — see my answer below).

How can I get my data changes to take affect in the view?

Это было полезно?

Решение

It turns out that calling scope.$apply() actually does work in the click handler. I'm not sure what I was doing which made me think that it didn't.

  elementShape.on('click', function() {
    console.log('Clicked in directive');
    scope.shape.fill = '#bada55';
    scope.$apply();
  });

I also made a mistake in my JSFiddle which compounded the problem. Here's a working fiddle.

Sorry if I wasted anyone's time!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top