When I use AngularJS directive in svg the result is not presented - angularjs

I'm trying to create AngularJS directive that I will use inside svg element.
The directive do not create svg element but use exist one.
I can see the right svg markup in the dev-tools but the browser does not display it.
Please see live example.
This is the directive:
angular.module('ui.directives', []).directive('svgText',
function() {
return {
restrict: 'E',
replace: true,
template: '<text fill="green" x="4" y="20" font-weight="bolder" font-size="2" font-family="Arial">89</text>'
};
}
);

This happens because jQuery (which AngularJS uses under the hood) doesn't know how to create svg elements. You can workaround this by adding a link function to the directive that clones the element that was created but creates it in the SVG namespace.
A potentially better method is to wrap your template in an SVG element (with namespace defined) and then in the link function pull out the child element and use that, it will already be created in the correct namespace.
module.directive(
'svgText',
function () {
return {
restrict: 'E',
template: '<svg xmlns="http://www.w3.org/2000/svg"><text fill="green" x="4" y="20" font-weight="bolder" font-size="2" font-family="Arial">89</text></svg>',
replace: true,
link: function (scope, elem, attrs) {
// Extract the child element to replace the SVG element.
var child = angular.element(elem[0].firstElementChild);
// Copy attributes into element.
for (attrName in attrs) {
if(typeof attrs[attrName] === "string") {
child.attr(attrName, attrs[attrName]);
}
}
elem.replaceWith(child );
}
};
}
);
I have published an article on AngularJS + SVG which talks through many such issues.
http://www.codeproject.com/Articles/709340/Implementing-a-Flowchart-with-SVG-and-AngularJS

Related

Accessing image data after the ngSrc gets resolved with an AngularJS directive

I'm trying to write an angularjs directive to process EXIF metadata in an img that is already loaded.
My desired usage:
<img ng-src="{{url}}" my-exif-directive />
My directive looks pretty basic:
...
restrict: 'A',
link: function(scope, element) {
var exifData = EXIF.readFromBinaryFile(/* ... */);
rotate(parseInt(exifData.Orientation || 1, 10), element);
}
...
Is there any way to tap into the image buffer?
When I debug it the ng-src parameter is still not resolved. Is this possible?
Depending on other directives (e.g. ng-repeat), attributes and scope properties may not be interpolated in link. The one can also notice that element.attr('attribute-name') may not be interpolated, while attrs.attributeName will.
Generally it is safe to do this:
link: function(scope, element) {
$timeout(function () {
...
});
}
If other directives don't use timeouts the similar way, directive's DOM is already there, as well as interpolated values.
Since there are bindings involved, and src may change with time, the proper way to handle it is:
link: function(scope, element, attrs) {
attrs.$observe('src', function (attr) {
if (!attr) return;
...
});
}

Recursive angular directive with large data

I have a angular directive to generate nested list structure. However when i get large data, browser gets stuck & is very slow. If it was only ng-repeat i could have used limitTo but this is a recursive template. Any suggestion please.
http://jsfiddle.net/L97o5swa/14/
treeModule.directive('tmTree', function() {
return {
restrict: 'E', // tells Angular to apply this to only html tag that is <tree>
replace: true, // tells Angular to replace <tree> by the whole template
scope: {
t: '=src',
fetchChildren: '&fetchChildren',
selectNode : '&selectNode' // create an isolated scope variable 't' and pass 'src' to it.
},
controller : function($scope){
console.log('aaa');
},
template: '<ul><branch ng-repeat="c in t.children" src="c" fetch-children="fetchChildren()" select-Node="selectNode({node :child})" ng-class="c.expandChildren ? \'\':\'collapsed\' "></branch></ul>' ,
link: function(scope, element, attrs) {
}
};
});
treeModule.directive('branch', function($compile) {
return {
restrict: 'E', // tells Angular to apply this to only html tag that is <branch>
replace: true, // tells Angular to replace <branch> by the whole template
scope: {
b: '=src',
fetchChildren: '&fetchChildren', // create an isolated scope variable 'b' and pass 'src' to it.
selectNode : '&selectNode'
},
controller : function($scope,$element){
} ,
template: '<li class="treeNode"><div class="wholerow"></div><span id="chevron-right" class="glyphicon glyphicon-chevron-right" ></span><a ng-click="selectNode({child : b})">{{ b.text }}</a></li>',
link: function(scope, element, attrs) {
//// Check if there are any children, otherwise we'll have infinite execution
var has_children = angular.isArray(scope.b.children);
var parent = scope.b;
//// Manipulate HTML in DOM
if (has_children) {
element.append($compile( '<tm-tree src="b" fetch-children="fetchChildren()" select-Node="selectNode({node:child})" ></tm-tree>')(scope) );
// recompile Angular because of manual appending
//$compile(element.contents())(scope);
}
var chevronRight = angular.element(element.children()[1]);
chevronRight.on('click',function(event) {
event.stopPropagation();
chevronRight.toggleClass('glyphicon-chevron-right');
chevronRight.toggleClass('glyphicon-chevron-down');
if(has_children){
element.toggleClass('collapsed');
if(scope.b.children.length == 0) {
}
}
});
}
};
});
Hard to tell based on the piece of code you posted. But my initial instinct is that you shouldn't be using so many jqLite references like element and append. You should handle more of this functionality within the template itself using ngRepeat (ie. if (has_children) { element.append...) and ngClick (ie. chevronRight.on('click'...). jqLite operations are expensive.

How to make this call in an angular scenario?

I'm using a youtube player called YTPlayer.
https://github.com/pupunzi/jquery.mb.YTPlayer
In this code he makes a JQuery call which works fine.
$(document).ready(function () {
$(".player").mb_YTPlayer();
});
How can i make such a call from my controller without using JQuery?
Thanks.
You create a directive. You can think of directives as extending html.
Your directive will look something like this:
.directive('ytPlayer', function() {
return {
scope: {
pathToVideo: '&'
},
link(scope, element, attr) {
//at this point, the DOM is ready and the element has been added to the page. It's safe to call mb_YTPlayer() here.
//also, element is already a jQuery object, so you don't need to wrap it in $()
element.mb_YTPlayer();
//scope.pathToVideo() will return '/video.mpg' here
}
}
}
And you'll add it to your page with this markup:
<yt-player path-to-video="/video.mpg"></yt-player>
It's OK to use jQuery inside of a directive if your video player is dependent on it. You should never need to use jQuery in an angular controller. If you find yourself doing so, you're not "thinking angular".
Many times, video players and other components require specific markup to work, so you can customize your template for the directive with the template property:
.directive('ytPlayer', function() {
return {
scope: {
pathToVideo: '&'
},
replace: true,
template: '<div><span></span></div>'
link(scope, element, attr) {
element.mb_YTPlayer();
//scope.pathToVideo() will return '/video.mpg' here
}
}
}
These two lines:
replace: true,
template: '<div><span></span></div>'
will cause angular to replace the yt-player markup with the markup in the template property.

How to prevent duplicated attributes in angular directive when replace=true

I've found that angular directives that specify replace: true will copy attributes from the directive usage into the output rendered by the template. If the template contains the same attribute, both the template attribute value and the directive attribute value will be combined together in the final output.
Directive usage:
<foo bar="one" baz="two"></foo>
Directive:
.directive('foo', function() {
return {
restrict: 'E',
replace: true,
template: '<div bar="{{bar}}" baz="baz"></div>',
scope: {
bar: '#'
},
link: function(scope, element, attrs, parentCtrl) {
scope.bar = scope.bar || 'bar';
}
};
})
Output:
<div bar="one " baz="two baz" class="ng-isolate-scope"></div>
The space in bar="one " is causing problems, as is multiple values in baz. Is there a way to alter this behavior? I realized I could use non-conflicting attributes in my directive and have both the template attributes and the non-conflicting attributes in the output. But I'd like to be able to use the same attribute names, and control the output of the template better.
I suppose I could use a link method with element.removeAttr() and element.attr(). It just seems like there should be a better solution.
Lastly, I realize there is talk of deprecating remove: true, but there are valid reasons for keeping it. In my case, I need it for directives that generate SVG tags using transclusion. See here for details:
https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb
No, there isn't some nice declarative way to tell Angular how x attribute should be merged or manipulated when transplanted into templates.
Angular actually does a straight copy of attributes from the source to the destination element (with a few exceptions) and merges attribute values. You can see this behaviour in the mergeTemplateAttributes function of the Angular compiler.
Since you can't change that behaviour, you can get some control over attributes and their values with the compile or link properties of the directive definition. It most likely makes more sense for you to do attribute manipulation in the compile phase rather than the link phase, since you want these attributes to be "ready" by the time any link functions run.
You can do something like this:
.directive('foo', function() {
return {
// ..
compile: compile
// ..
};
function compile(tElement, tAttrs) {
// destination element you want to manipulate attrs on
var destEl = tElement.find(...);
angular.forEach(tAttrs, function (value, key) {
manipulateAttr(tElement, destEl, key);
})
var postLinkFn = function(scope, element, attrs) {
// your link function
// ...
}
return postLinkFn;
}
function manipulateAttr(src, dest, attrName) {
// do your manipulation
// ...
}
})
It would be helpful to know how you expect the values to be merged. Does the template take priority, the element, or is some kind of merge needed?
Lacking that I can only make an assumption, the below code assumes you want to remove attributes from the template that exist on the element.
.directive('foo', function() {
return {
restrict: 'E',
replace: true,
template: function(element, attrs) {
var template = '<div bar="{{bar}}" baz="baz"></div>';
template = angular.element(template);
Object.keys(attrs.$attr).forEach(function(attr) {\
// Remove all attributes on the element from the template before returning it.
template.removeAttr(attrs.$attr[attr]);
});
return template;
},
scope: {
bar: '#'
}
};
})

When does an SVG element in a directive get updated?

Consider a directive of type "svg" that uses a template to render an SVG element within an SVG in the existing DOM:
angular.module('myapp').directive('component', ['$timeout', function($timeout){
return {
type: 'svg',
restrict: 'E',
replace: true,
templateUrl: 'template.svg',
link: function (scope, element) {
var dir = angular.element(document.createElement('my-directive'));
dir.attr('id', 'test');
$compile( dir )( scope );
element.append(dir);
$timeout(function(){
var el = document.getElementById('test');
var bb = el.getBoundingClientRect();
scope.rectWidth = bb.width;
scope.rectHeight = bb.height;
}, 0);
}
};
}]);
Here is the template:
<rect width="{{rectWidth}}" height="{{rectHeight}}"></rect>
The intention is that the dimensions are updated after the DOM has been rendered. The dimensions aren't updated about half of the time - especially with more than a few of this directive element on the page. I have read that $timeout will ensure that the HTML DOM is rendered before the function runs. How can I ensure that the SVG has updated before I measure the appended element's size?
Listening for DOMSubtreeModified on the element solves this for the time being, but that event is deprecated. The proper way to solve this is to use the MutationObserver interface.

Resources