ng-repeat always rendering as last in directive template - javascript

I have a directive (<my-directive>) and child directives (<my-child-directive>) as below structure:
<my-directive>
<my-child-directive caption="{{student}}" ng-repeat="student in students"></my-child-directive>
<my-child-directive caption="static content1"></my-child-directive>
</my-directive>
The first child directive repeated using ng-repeat and The last child directive is a static item.
Now I have two questions here:
Question1:
In the final output, the last directive is rendering as first <li>. is there any way to render the <li>s in the same order of child directives?
Question2:
I have used a hidden div in <my-directive>'s template to render the transclude for temporary purpose. Is there any way to avoid this unwanted div?
here is my java script code:
app=angular.module('myApp', [])
app.controller("MyController",function($scope){
$scope.students=["Alex","George","Phillip"];
});
app.directive('myDirective',function(){
return{
restrict:'E',
transclude:true,
template:"<ul><li ng-repeat='item in items'>{{item.caption}}</li></ul> <div ng-transclude ng-hide='true'></div>",
controller:function($scope){
$scope.items=[];
this.addItem=function(subScope){
$scope.items.push(subScope);
}
},
link:function(scope,element,attrs){
}
};
});
app.directive('myChildDirective',function () {
return {
restrict: 'E',
require:"^myDirective",
scope:{
caption:"#"
},
link:function(scope,element,attrs,parentCtrl){
parentCtrl.addItem(scope);
}
}
})
fiddle: http://jsfiddle.net/fyds082s/5/
Can anybody help on this?

The link function of the second directive is executed before the directives that come before him. When you check the ng-repeat state while the static directive is executed it shows that the index is 0. it hasn't finished compiling the ng-repeat yet.
My guess is that angular links sibling element from last to first, the same way it links from child elements first
Update:
My guess is wrong. so my new guess it has something to do with child scopes. For example, when adding ng-if to the second directive it is indeed executed second: http://jsfiddle.net/fyds082s/7/
<my-directive>
<my-child-directive caption="{{student}}" ng-repeat="student in students"></my-child-directive>
<my-child-directive caption="static content1" ng-if="true"></my-child-directive>
</my-directive>

What order do you think the following would produce?
<my-directive>
<my-child-directive caption="one" ng-if="true"></my-child-directive>
<my-child-directive caption="two"></my-child-directive>
</my-directive>
If you answered two, one then hat's off to you.
The reason behind this somewhat unintuitive result is a "conditional rendering" directive like ng-if and ng-repeat. They both transclude their hosting element, but render it later - when $scope.$watch or $scope.$watchCollection fire an event, respectively - at which time the link function of myChildDirective is called.
The best approach is not to rely on the order of called link functions to determine the order of rendering.

Related

AngularJS Directive - Multiple directives same name

Let's say I have multiple directives with the same name "parent-elem" (on each page I can have a different number of these directives - dynamic number)
<div ng-app="app">
<div parent-elem></div>
<div parent-elem></div>
<div parent-elem></div>
</div>
Is there a way to know (inside the link function) AngularJS finished render all the directives with the same name on the page?
Inside the directive link function - how do I know this directive is the last rendered?
Note: Not using a ng-repeat
You should have had written, same directive, multiple times :P
Now, that totally depends on your directive, how you make it.
It can keep the counter in a service or rootScope may be whenever it is initialized.
If you are using ng-repeat which you should in this case, you have a bool $last that can tell you if the element rendered is last or not, which you can pass to the directive via any attribute.

Getting element width inside directive the Angular way

I have a directive which contains an element inside it with width: 100%. However, the directive must know the clientWidth of that element -- that is, in pixels rather than percent. The element is buried deep in the directive's DOM.
The containing controller may have multiple instances of this directive, so doing document.getElementsByClassName('myclass')[0].clientWidth won't work. Selecting the directive's element works, but the code for that looks similar to element.children().children().children()[32].clientWidth, which doesn't look very stable. What is the right, Angular way of doing this?
Whenever you need to do any DOM manipulation in Angular, directives are the way to go. The "Angular" approach would be to use your directive's link function.
DEMO: http://plnkr.co/edit/83aZUmsRzuTUFotxUYSB?p=preview
Template Use Case:
<div element-width></div>
Directive Example:
angular.module('myApp').directive('elementWidth',function(){
return {
restrict: "A",
link:function($scope, element){
// element param being passed in refers
// to the element the directive is bound to, in this case, the DIV
// do something with the element width here
}
}
});
Hope this helps and let me know if you have any questions.
I figured it out. Although single elements do not have .getElementsByClassName, you can still use .querySelector. So my finished code was:
element[0].querySelector('.myclass').clientWidth

Run code after transcluded code is linked in Angular

I have directive with transclude and when link is executed I always have {{ }} when I try to do jQuery with $element. I have html like this:
<outter>
<inner ng-repeat="item in items" value="{{item.id}}">
{{item.name}}
</inner>
</outter>
and inside inner link function when I run
console.log($element.html());
I've got <li ng-repeat="item in items"><a>{{item.name}}</a></li> (my template is <li><a>).
inside outter $element in link function there is one ng-repeat
I need to initialize my directive when everything is in place. Is there there a way to do this in Angular? I've try post link (but I think is the same as just link) also transclude using a function. and everytime I got template that's not interpolated even that scope is updated, they are never in sync.
In my code (not in demo) I'm using ngModelController and $render is also rendered before everything is updated.
plunker

How to manipulate DOM after ng-show in Angularjs?

I'm new to angularjs. I'm using angularjs version 1.2.13.
I've come upon a scenario where after ng-show displays my DIV, I need to manipulate the DOM in order to realign some DIV columns. I cannot realign my DIVs if they are hidden.
HTML looks like this:
<div ng-controller="MyController" ng-show="IsThisShown">
<div class="column">1</div>
<div class="column">2</div>
<div class="column">3</div>
</div>
I think the way to go would be to create a "realign" directive such as:
<div class="column" realign>1</div>
<div class="column" realign>2</div>
<div class="column" realign>3</div>
But I'm not sure how I would make the directive trigger only when the parent DIV is shown (through the ng-show directive).
Any ideas ?
Thanks appreciated!
Here's a little more code:
module.controller("MyController", function($scope, FormState){
$scope.$watch(function(){ return FormState.showGrid; }, function(newVal, oldVal){
//At this point in time, right here, the DOM has not been updated...
$scope.IsThisShown = newVal;
//At this point in time, right here the DOM has still not been updated...
//the DOM gets updated when the function exists
});
});
You have a few different options.
One option you can explore is using an isolate scope with your realign directive.
See this example here:
http://plnkr.co/edit/lO2U4GZcEm4K1qGLpsFV
You don't have to use the isolate scope as Angular scope is prototypical by nature (though with a slight gotcha with regards to primitives), but I figured I'd throw this into the example so you can see isolate scope in action. In this example I'm using an isolate scope with an execute expression.
Just a random example that builds on your description by randomly repositioning some div's after the parent is shown.
Hopefully that helps.
There are a lot of ways, but the way that immediately comes to mind is:
<div ng-controller="MyController" ng-show="IsThisShown">
<div class="column" realign="IsThisShown">1</div>
<div class="column" realign="IsThisShown">2</div>
<div class="column" realign="IsThisShown">3</div>
</div>
And have your new directive observe its argument and do the realignment when it goes true.
The other answers are good (and standard), but tie the realign directive to being shown/hidden by that variable. If, for example, there are two nested ng-show's, this would break down. In my opinion, a better solution is to create an on-show directive, with the following scope:
scope: { 'onShow' : '&' }
And within the link function, set up a $scope.$watch as follows:
$scope.$watch(
function() { return $element.hasClass('ng-hide'); },
function(newVal, oldVal) { /* execute onShow here if newVal === false */ }
);
You could also (and I don't recommend this) change ng-show to ng-if. ng-if removes and adds the elements from the DOM, instead of just hiding them. In that case, the link function fires every time the element reappears.

Angular.js directive transclude ngRepeat - error

I'm trying to create a chart directive ( not interested in existing solutions ). I want to have definable templates for each bar using a sub-directive of bartemplate. I want to iterate the bartemplate so I thought using a template with ng-repeat and ng-transclude would do the trick ... instead angular threw an error.
Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <div ng-repeat="bar in bars track by $index" ng-transclude="" class="ng-scope">
http://jsfiddle.net/jt4Y2/7/
So you may ask, why the transclude? I want to be able to include any element I add to the chart (ie. a label ) and not interfere with the <bartemplate>.
You have several problems with your code. First you are complicating it by doing transclusions withing transclusions within transclusions. Second, you are combining directives that already do transclusions such as ng-repeat and trying to transclude within it, which just won't work.
I have provided a simplified version of your code that does what you want:
http://jsfiddle.net/jt4Y2/6/
Let me try to explain what it is doing. First we have the barchart directive. I took the liberty of simplifying but just making the entire body act as the template for a bar rather than having a bar-template directive.
Because we have set transclude: true the actual contents inside the barchart div are stripped out of the dom, but made available through a transclude function that you can inject into the controller via the $transclude parameter. We take this function and save it on our controller, so we can access it later on.
The barchart directive is then replaced by this template:
<div xmlns="http://www.w3.org/2000/svg">
<div ng-repeat="bar in bars track by $index" render-bar></div>
</div>
Notice that there is no ng-transclude. Instead we create our own directive render-bar to render the template (this avoids any conflicts with ng-repeat and it's own transclusion).
The renderBar directive is pretty simple:
directive('renderBar', function(){
return {
require: '^barchart',
link: function(scope, element, attrs, controller){
controller.renderBarTemplate(scope, function(dom){
element.append(dom);
});
}
}
});
First, we require that there be a parent barchart directive. In the link function, we can then get its controller and access the stored transclude function which we named renderBarTemplate.
The first parameter we pass is the scope, which makes the function link against the current scope (basically the scope provided by ng-repeat which gives us access to the iteration variables including bar).
The compiled dom is returned via the a callback (the second paramter), which we can then attach to the <div> provided by ng-repeat.
Let me know if you have any questions or need additional clarification.
UPDATE:
Here's a version that let's you keep the <bartemplate> directive:
http://jsfiddle.net/jt4Y2/10/
I added the bartemplate directive:
directive('bartemplate', function(){
return {
transclude: true,
restrict: 'E',
require: '^barchart',
link: function(scope, element, attrs, barChartController, transclude){
element.remove();
barChartController.renderBarTemplate=transclude;
}
};
})
It is now responsible for taking its transclude function and storing it in the parent's controller. (so it can later be use by the render-bar directive.
Also notice the element.remove(). This is not absolutely necessary but is simply removes the remaning <bartemplate> tag from the html.

Categories