add svg element at runtime in angular with 2 way data binding - angularjs

I want to add svg element line at runtime with ng-attr-x1={{some scope varaible}}.
I tried 2 ways:
In 1 way I tried with $compile:
var g=angular.element(document.createElementNS("http://www.w3.org/2000/svg", "g"));
var line=$compile('<line ng-attr-x1={{$scope.array[array.length-1].x1}} ng-attr-y1={{$scope.array[array.length-1].y1}} ng-attr-x1={{$scope.array[array.length-1].x2}} ng-attr-x1={{$scope.array[array.length-1].y2}} style="with mandatory styling for line">')($scope);
g.append(line);
parentg.append(g);
In this method line is not showing and g is showing with 0px height and width.
In a 2 way I treid like :
var line=angular.element(document.createElementNS("http://www.w3.org/2000/svg", "line"));
line.attr('ng-attr-x1','scopeVariable');
line.attr('ng-attr-x2','scopeVariable');
line.attr('ng-attr-y1','scopeVariable');
line.attr('ng-attr-Y2','scopeVariable');
In this ng-attr attributes does not resolved to x and y. In DOM it shows as

This is possibly coming in way too late to help you, but I was stuck for a little while on the same question.
Turns out that $compile can take an element created with document.createElementNS() - like this:
var actApp = angular.module('actApp', []);
actApp.controller('shapeController', ['$scope', '$compile',
function shapeController($scope, $compile) {
$scope.color = 'green';
var svgEle = $(document.getElementById('mySvgElement'));
var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', '0');
line.setAttribute('x2', '0');
line.setAttribute('x2', '20');
line.setAttribute('y2', '20');
line.setAttribute('style', 'stroke: {{color}};');
svgEle.append($compile(line)($scope));
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="actApp">
<div ng-controller="shapeController">
<input type="button" ng-click="color='blue'" value="To blue" />
<br />
<svg id="mySvgElement" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
</svg>
</div>
</div>
Hope this saves someone a little bit of time in the future.

Related

aframe physics collision event not detected in angular controller

I am trying to detect a collision event between a dynamic body and a static body and using Don Mccurdy's physics system.
The "collide" event is getting successfully invoked on collision but the subsequent function fails with the error "cannot read property 'body' of undefined for e"
My HTML source
<div ng-app="webVrApp">
<div ng-controller="trying">
<a-scene physics="debug: true">
<a-entity id='ball' position="0 1 -4" material="color:green;"
geometry="primitive:sphere; radius: 0.5;" dynamic-body></a-entity>
<a-plane color='red' static-body rotation="-90 0 0" width="100"
height="100"></a-plane>
<a-entity camera look-controls position="0 1.5 0">
<a-cursor></a-cursor>
<a-entity position="0 0 -3" id="weapon" static-body>
<a-box color='blue' width='0.25' height='0.5' depth='3' static-
body></a-box>
</a-entity>
</a-entity>
</a-scene>
</div>
</div>
and the controller code
app.controller('trying', ['$scope', function($scope){
var $ = function (sel){document.querySelector(sel)};
document.querySelector('#weapon').addEventListener('collide', function(e) {
const ball = $("#ball");
if(e.detail.body.id === ball.body.id) { //error comes for this line unknown 'body'
alert("collision happened");
};
});
}]);
Here is a codepen for this - codepen
Please help.
I'm using aframe-aabb-collider-component to detect colision between two objects, try it. https://www.npmjs.com/package/aframe-aabb-collider-component

Make multiple small rectangles inside big rectangle using svg and ng-repeat

I have this code where I need to display multiple small rectangles inside a big rectangle and I need to do this entire process multiple times.
here is my data:
"data": {
"rect1": {
"a":[10,20],
"b":[35,10]
},
"rect2": {
"y":[25,10],
"z":[55,20]
}
}
This data should make two rectangles rect1 and rect2 and two rectangles inside each of them a,b and y,z respectively. each small rectangle has start position x and width of that small rectangle for example a starts at x 10 and width=20.
<ul>
<li ng-repeat="(rect,coords) in data">
<svg>
<rect x=1 y=1 width=1000 height=50 style="fill:grey;" />
<span ng-repeat="coord in coords">
<rect x={{coord[0]}} y=1 width={{coord[1]}} height=50 style="fill:blue;" />
enter code here
But this code is not working as I have added ng-repeat line between the two tags.
image of what the final result should look like
I made this image in powerpoint so ignore the background.
You were pretty close. You can't use <span> inside an SVG. But most of the rest was correct.
Also it is better to use ng-attr-x="{{value}} instead of x="{{value}}. Otherwise the SVG parser will throw errors because it doesn't understand the string "{{value}}".
Here is a working example.
var app = angular.module('myApp', [])
app.controller("AppCtrl", ["$scope", function($scope) {
$scope.data = {
"rect1": {
"a":[10,20],
"b":[35,10]
},
"rect2": {
"y":[25,10],
"z":[55,20]
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<ul ng-controller="AppCtrl">
<li ng-repeat="(rectName, coords) in data">
<svg id="{{rectName}}" width="100%" height="50">
<rect x="1" y="1" width="1000" height="50"
style="fill: lightgrey;" />
<rect ng-repeat="(label, coord) in coords"
ng-attr-x="{{coord[0]}}" y="1"
ng-attr-width="{{coord[1]}}" height="50"
style="fill: blue;" />
<text ng-repeat="(label, coord) in coords"
ng-attr-x="{{coord[0]}}" y="25"
style="fill: white;">{{label}}</text>
</svg>
</li>
</ul>
</div>

Reference Angular binding from javascript

I'm looking for a (best-practice) way to iterate through a list of elements in the scope of an angular controller and generate a div with an element specific id and append a svg to the element specific div. I'm very new to Angular...and suspect that the following attempt fails because I misunderstand Angular bindings?
What is a better way to do the following:
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}>
<script type="text/javascript">
var svg_img = build_svg(args);
document.getElementById({{item.id}}).appendChild(svg_img);
</script>
</div>
</div>
</div>
Thanks!
You should place your logic inside of your controller and conditionally render as much html as necessary rather than invoking a script tag inside of an ng-repeat..
<div ng-controller="YourCtrl">
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}></div>
<div ng-bind-html="$scope.buildSvg(item)">
</div>
</div>
</div>
</div>
In your angular controller, you would then add a function to build out and return the svg for you to render.
app.controller('YourCtrl', ['$scope', function ($scope) {
$scope.buildSvg = function (item) {
// add logic here.
}
});
What does your function build_svg return?
We'd need a little more information about the kind of end-result you would like to get.
But yeah, it's not really good practice to have a script element within a ng-repeat directive.
I see two solutions here:
1- Build your SVG directly within the ng-repeat
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}>
<svg height="{{item.svg.attrs.height}}" width="{{item.svg.attrs.width}}">
<circle cx="50" cy="50" r="40" stroke="black" stroke- width="3" fill="red" />
</svg>
</div>
</div>
</div>
Here is a plunker of this method:
http://plnkr.co/edit/g58BUPScjKHjRLAfx6ks?p=preview
2- Create a directive to generate your SVG with some additional parameters and flexibility.
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}>
<my-svg attrs="item.svg.attrs"></my-svg>
</div>
</div>
</div>
The my-svg directive would generate a SVG element with the attrs parameters.

SVG images not showing when their size is set through ng-attr-width or ng-style?

(N.B. This is a follow-on from another question.)
Here is some simple HTML, CSS, and AngularJS to display three pictures of bullfinches from flickr:
<html ng-app="AngularSVGTestApp">
<head>
<title>Angular SVG Image Size Test</title>
<style>
svg {
background-color: aqua;
}
</style>
</head>
<body ng-controller="MainCtrl as mainCtrl">
<div ng-cloak>
<svg ng-show="mainCtrl.svg.show" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" width="200" height="400">
<!-- https://www.flickr.com/photos/yeliseev/10116904936 -->
<image xlink:href="https://farm6.staticflickr.com/5535/10116904936_870f94488d_m.jpg"
x="20" y="20"
width="100" height="100"/>
<!-- https://www.flickr.com/photos/yeliseev/112992806 -->
<image xlink:href="https://farm1.staticflickr.com/38/112992806_780ebcd0ce_m.jpg"
x="20" y="140"
ng-attr-width="mainCtrl.svg.style.width"
ng-attr-height="mainCtrl.svg.style.height" />
<!-- https://www.flickr.com/photos/yeliseev/4433515854 -->
<image xlink:href="https://farm5.staticflickr.com/4058/4433515854_41152445a9_m.jpg"
x="20" y="260"
ng-style="mainCtrl.svg.style" />
</svg>
</div>
<script src="/Scripts/angular.js"></script>
<script>
angular.module('AngularSVGTestApp', [])
.controller('MainCtrl', [function () {
var self = this;
self.svg = {
show: true,
style: {
width: 100,
height: 100
}
};
}]);
</script>
</body>
</html>
fiddle
The three bullfinch images within the SVG differ in how their size is set.
Has its width and height set to 100 explicitly in the HTML
Has its width and height set to 100 using ng-attr-width and ng-attr-height to bind to model values held in the controller
Has its width and height set to 100 using ng-style to bind to model values held in the controller
Why don't either of these latter two methods work? What should I use to bind the dimensions of an image in an SVG from AngularJS?
it should be like, here is the doc
ng-attr-width="{{mainCtrl.svg.style.width}}"
ng-attr-height="{{mainCtrl.svg.style.height}}"
jsFiddle
as for ngStyle
here is the answer . In short, I don't think you can do that with ngStyle.
Here is some ref. It shows you how to do it in CSS correctly.

Angular and SVG filters

I came accross a strange behaviour when using SVG with AngularJS. I'm using the $routeProvider service to configure my routes. When I put this simple SVG in my templates, everything is fine:
<div id="my-template">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect fill="red" height="200" width="300" />
</svg>
// ...
</div>
But when I add a filter, with this code for instance:
<div id="my-template">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="blurred">
<feGaussianBlur stdDeviation="5"/>
</filter>
</defs>
<rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
</div>
Then:
It works on my homepage.
With Firefox, the SVG isn't visible anymore on the other pages, but it still leaves space where it would have been. With Chrome, the SVG is visible, but not blurred at all.
The SVG is visible again when I remove manually (with Firebug) the filter style.
Here is the routes configuration:
$routeProvider
.when('/site/other-page/', {
templateUrl : 'view/Site/OtherPage.html',
controller : 'Site.OtherPage'
})
.when('/', {
templateUrl : 'view/Site/Home.html',
controller : 'Site.Home'
})
.otherwise({
redirectTo : '/'
})
;
Fiddle
Please notice that I've failed to reproduce the problem with Chrome in a Fiddle, although it "works" with Firefox.
I've tried to no avail to create my whole SVG with document.createElementNS().
Does someone has an idea of what is happening?
The problem
The problem is that there is a <base> tag in my HTML page. Therefore, the IRI used to identify the filter is not anymore relative to the current page, but to the URL indicated in the <base> tag.
This URL was also the URL of my home page, http://example.com/my-folder/ for instance.
For the pages other than the home page, http://example.com/my-folder/site/other-page/ for example, #blurred was computed to the absolute URL http://example.com/my-folder/#blurred. But for a simple GET request, without JavaScript, and therefore without AngularJS, this is simply my base page, with no template loaded. Thus, the #blurred filter doesn't exist on this pages.
In such cases, Firefox doesn't render the <rect> (which is the normal behaviour, see the W3C recommandation). Chrome simply doesn't apply the filter.
For the home page, #blurred is also computed to the absolute URL http://example.com/my-folder/#blurred. But this time, this is also the current URL. There is no need to send a GET request, and thus the #blurred filter exists.
I should have seen the additional request to http://example.com/my-folder/, but in my defense, it was lost in a plethora of other requests to JavaScript files.
The solution
If the <base> tag is mandatory, the solution is to use an absolute IRI to identify the filter. With the help of AngularJS, this is pretty simple. In the controller or in the directive that is linked to the SVG, inject the $location service and use the absUrl() getter:
$scope.absUrl = $location.absUrl();
Now, in the SVG, just use this property:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="blurred">
<feGaussianBlur stdDeviation="5"/>
</filter>
</defs>
<rect style="filter:url({{absUrl}}#blurred)" fill="red" height="200" width="300" />
</svg>
Related: SVG Gradient turns black when there is a BASE tag in HTML page?
It looks like a behaviour I observed before. The root cause is that you end up having multiple elements (filters) with the same id (blurred). Different browsers handle it differently...
Here is what I did to reproduce your case: http://jsfiddle.net/z5cwZ/
It has two svg and one is hidden, firefox shows none.
There are two possibilities to avoid conflicting ids. First you can generate unique ids from your template (I can't help in doing it with angularjs tough). Here is an example: http://jsfiddle.net/KbCLB/1/
Second possibility, and it may be easier with angularjs, is to put the filter outside of the individual svgs (http://jsfiddle.net/zAbgr/1/):
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0" height="0">
<defs>
<filter id="blurred">
<feGaussianBlur stdDeviation="10" />
</filter>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="display:none">
<rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
<br/>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
Just ran into this problem. Expanding from #Blackhole's answer, my solution was to add a directive that changes the value of every fill attribute.
angular.module('myApp').directive('fill', function(
$location
) { 'use strict';
var absUrl = 'url(' + $location.absUrl() + '#';
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
$attrs.$set('fill', $attrs.fill.replace(/url\(#/g, absUrl));
}
};
});
I recently ran into this issue myself, if the svg is used on different routes you will have to add the $location to each path.
A better way to implement this is to just use a window.location.href instead of the $location service.
Also had issues with svg link:hrefs, and yes the problem is because of the <base> - I had errors being thrown when concatenating the absolute url because of the Strict Contextual Escaping restrictions.
My solution was to write a filter that added the absolute url and also trusted the concatenation.
angular.module('myApp').filter('absUrl', ['$location', '$sce', function ($location, $sce) {
return function (id) {
return $sce.trustAsResourceUrl($location.absUrl() + id);
};
}]);
And then use it like this: {{'#SVGID_67_' | absUrl}
Result : http://www.example.com/deep/url/to/page#SVGID_67_ and no $sce errors

Resources