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.
Related
in my application i have a controller and a directive which i use to draw a chart.
so my model is like this: $scope.d3DataGraph ={"selected":{"node":"","impiega":[],"impiegato": []} , "nodes":[],"links":[]};
in the controller i've set up a function that adds some data to the model:
$scope.d3DataGraph.nodes.push(articolo);
then i have the directive which is responsible to draw the graph by adding some svg tags to the dom:
in my directive i have a render function that have to be triggered when the model changed...
angular.module('myApp.directives')
.directive('d3Graph', ['d3', function(d3) {
return {
restrict: 'EA',
scope: {
data: "=",
query: "=",
label: "#",
onClick: "&"
},
link: function(scope, iElement, iAttrs) {
var svg = d3.select(iElement[0]).append("svg")
.attr("width", "100%")
.attr("height", "800px");
var datatree = {};
scope.$watch(function(){ return scope.data.nodes; }, function(){
return scope.render(scope.data, scope.query);
}
);
scope.render = function(datatreex, query){....
the directive is "called" whit this tag
<d3-graph data="d3DataGraph" selected = "selected" query = "selezionati"></d3-graph>
the problem is that the render function is called only when the page is loaded, but not when the controller updates the model ...
where i get wrong?
the overall set up seems to be correct, what do you think of it?
That's because $watch is just watching the reference of scope.data.nodes, so no matter what you push or pop, the reference will not change.
Instead of using $watch, you can use $watchCollection. It will detect the length of the array.
scope.$watchCollection('data.nodes', function(){
return scope.render(scope.data, scope.query);
});
I need a small help in angularjs,
please have a look on this
code (chrome browser):
http://jsfiddle.net/Aravind00kumar/CrJn3/
<div ng-controller="mainCtrl">
<ul id="names">
<li ng-repeat="item in Items track by $index">{{item.name}} </li>
</ul>
<ak-test items="Items">
</ak-test>
</br>
<div id="result">
</div>
</div>
var app = angular.module("app",[]);
app.controller("mainCtrl",["$scope",function($scope){
$scope.Items = [
{name:"Aravind",company:"foo"},
{name:"Andy",company:"ts"},
{name:"Lori",company:"ts"},
{name:"Royce",company:"ts"},
];
$scope.Title = "Main";
}]);
app.directive("akTest",["$compile",function($compile){
return {
restrict: 'E',
replace: true,
scope: {
items: "="
},
link: function (scope, element, attrs) {
// var e =$compile('<li ng-repeat="item in Items track by $index">{{item.name}} </li>')(scope);
// $("#names").append(e);
var lilength = $("#names li").length;
var html ='<div> from angular ak-test directive: '+lilength+'</div>';
element.replaceWith(html);
}
};
}]);
$(function(){
$("#result").html('from jquery: '+$("#names li").length);
});
I have created a custom directive and trying to access an element from the view which in the ng-repeat above my custom directive
The problem is, in the directive it was saying ng-repeat not rendered yet.
Here is the problem
I have two elements
<svg>
<g>
List of elements
</g>
<g>
Based on the above rendered elements I have to draw a line between elements like a connection. I have to wait till the above elements to get render then only I can read the x,y positions and can draw a line.
</g>
</svg>
Both elements and the connections are scope variables. As per my understanding both are in the same scope and execution flow starts from parent to child and finishes from child to parent. How can I force above ng-repeat rendering part to complete before starting the custom directive?
is there any alternative available in angular to solve this dependency?
It's been a while, so my Angular is getting a bit rusty. But if I understand your problem correctly, it's one that I have run into a few times. It seems that you want to delay processing some elements of your markup until others have fully rendered. You have a few options for doing this:
You can use timeouts to wait for the page to render:
$timeout(function() {
// do some work here after page loads
}, 0);
This generally works ok, but can cause your page to flash unpleasantly.
You can have some of your code render in a later digest cycle using $evalAsync:
There is a good post on that topic here: AngularJS : $evalAsync vs $timeout. Typically, I prefer this option as it does not suffer from the same page flashing issue.
Alternatively, you can look for ways to refactor your directives so that the dependent parts are not so isolated. Whether that option would help depends a lot on the larger context of your application and how reusable you want these parts to be.
Hope that helps!
I would create a directive for the whole list, and maybe a nested directive for each list item. That would give you more control I would think.
Thanks a lot for your quick response #Royce and #Lori
I found this problem causing because of ng-repeat I have solved it in the following way..
Created a custom directive for list elements and rendered all elements in a for loop before the other directive start. This fix solved the problem temporarily but i'll try the $evalAsync and $timeout too :)
var app = angular.module("app",[]);
app.controller("mainCtrl",["$scope",function($scope){
$scope.Items = [
{name:"Aravind",company:"foo"},
{name:"Andy",company:"ts"},
{name:"Lori",company:"ts"},
{name:"Royce",company:"ts"},
];
$scope.Title = "Main";
}]);
app.directive("akList",["$compile",function($compile){
return {
restrict: 'A',
replace : false,
link: function (scope, element, attrs) {
var _renderListItems = function(){
$(element).empty();
for(var i=0;i<scope.Items.length; i++)
{
var li ='<li> '+ scope.Items[i].name +' </li>';
element.append(li);
}
};
_renderListItems(scope);
scope.$watch('Items.length', function (o, n) {
_renderListItems(scope);
}, true);
}};}]);
app.directive("akTest",["$compile",function($compile){
return {
restrict: 'E',
replace: true,
scope: {
items: "="
},
link: function (scope, element, attrs) {
var lilength = $("#names li").length;
var html ='<div> from angular ak-test directive: '+lilength+'</div>';
element.replaceWith(html);
}
};
}]);
$(function(){
$("#result").html('from jquery: '+$("#names li").length);
});
What is the correct way to insert element into the DOM using angular.element() ?
app.directive('validationTest', function(){
return {
restrict: 'A',
replace: false,
link: function(scope, element, attrs){
scope.call = function(){
console.log('Someone clicked it');
};
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
element.append(newElement);
}
};
});
In this code, I am trying to add a span element within the element on which directive has been applied. I am able to add this span element as the child of the parent div on which append mehtod is called.
However, as you can see in the code, a ng-click also has been associated with this span. I know it is not useful from any point of view, it is just for demo purpose. So, normally, clicking on this span, a line should be printed in the console. However, it doesn't happen.
What am I missing here ? Have I used wrong approach for this append or there is some error in my code ?
If the HTML that you dynamically append to the DOM has directives, then you'll want to $compile and link it before appending it to the DOM:
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
$compile(newElement)(scope);
element.append(newElement);
An alternative approach that is less error prone is to move your DOM manipulation to the compile function. By inserting the new element during the compile phase, the new HTML will be automatically linked during the linking phase (no manual compile /link required):
app.directive('myDirective', function() {
return {
compile: function(element, attr) {
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
element.append(newElement);
return function(scope, element, attr) {
}
}
}
});
You're missing angular compilation. The compilation traverses your DOM looking for directives and initializes them. Without calling compile, nothing will initialize your ngClick. Try this to use $compile:
app.directive('validationTest', ['$compile', function($compile){
return {
restrict: 'A',
replace: false,
link: function(scope, element, attrs){
scope.call = function(){
console.log('Someone clicked it');
};
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
element.append(newElement);
$compile(element)(scope);
}
};
});
For further information: https://docs.angularjs.org/api/ng/service/$compile
It's better to use templateUrl or template.
View $compile docs.
I'm working with Angular and I'm having trouble doing something that I normally use jQuery for.
I want to bind a click event to an element and on click, slide a sibling element down and up.
This is what the jQuery would look like:
$('element').click(function() {
$(this).siblings('element').slideToggle();
});
Using Angular I have added an ng-click attribute with a function in my markup:
<div ng-click="events.displaySibling()"></div>
And this is what my controller looks like:
app.controller('myController', ['$scope', function($scope) {
$scope.events = {};
$scope.events.displaySibling = function() {
console.log('clicked');
}
}]);
So far this is working as expected but I don't know how to accomplish the slide. Any help is very much appreciated.
Update
I have replaced what I had with a directive.
My markup now looks like this:
<div class="wrapper padding myevent"></div>
I have removed what I had in my controller and have created a new directive.
app.directive('myevent', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.bind('click', function($event) {
element.parent().children('ul').slideToggle();
});
}
}
});
However, I still can't get the slide toggle to work. I don't believe slideToggle() is supported by Angular. Any suggestions?
I'm not sure exactly on the behaviour that you're talking about, but I would encourage you to think in a slightly different way. Less jQuery, more angular.
That is, have your html something like this:
<div ng-click="visible = !visible"></div>
<div ng-show="visible">I am the sibling!</div>
You can then use the build in ng-animate to make the sibling slide - yearofmoo has an excellent overview of how $animate works.
This example is simple enough that you can put the display logic in the html, but I would otherwise encourage you to as a rule to put it into the controller, like this:
<div ng-click="toggleSibling()"></div>
<div ng-show="visible"></div>
Controller:
app.controller('SiblingExample', function($scope){
$scope.visible = false;
$scope.toggleSibling = function(){
$scope.visible = !$scope.visible;
}
});
This kind of component is also a prime candidate for a directive, which would package it all up neatly.
app.directive('slideMySibling', [function(){
// Runs during compile
return {
// name: '',
// priority: 1,
// terminal: true,
// scope: {}, // {} = isolate, true = child, false/undefined = no change
// controller: function($scope, $element, $attrs, $transclude) {},
// require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements
restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
// template: '',
// templateUrl: '',
// replace: true,
// transclude: true,
// compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
link: function($scope, iElm, iAttrs, controller) {
iElm.bind("click", function(){
$(this).siblings('element').slideToggle();
})
}
};
}]);
Usage would be something like
<div slide-my-sibling><button>Some thing</button></div><div>Some sibling</div>
Note all the "code" above is just for the sake of example and hasn't been actually tested.
http://plnkr.co/edit/qd2CCXR3mjWavfZ1RHgA
Here's a sample Plnkr though as mentioned in the comments this isn't an ideal setup since it still has the javascript making assumptions about the structure of the view so ideally you would do this with a few directives where one requires the other or by using events (see $emit, $broadcast, $on).
You could also have the directive create the children "programmatically" via JavaScript and circumvent the issue of not knowing what context the directive is being used in. There are a lot of potential ways to solve these issues though none of the issues will stop it from functionally working they are worth noting for the sake of re-usability, stability, testing, etc.
As per this link : https://docs.angularjs.org/api/ng/function/angular.element
AngularJs element in your code snippet represents JQuery DOM object for related element. If you want to use JQuery functions, you should use JQuery library before angular loads. For more detail, please go through above link.
Best practice:
<div ng-if="view"></div>
$scope.view = true;
$scope.toggle = function(){
$scope.view = ($scope.view) ? false : true;
}
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