Order AngularJS directives are created not as I understand from the documentation - angularjs

I have an AngularJS app where I have to traverse some of the elements with the cursor keys. To do this I created a directive called selectable that adds some info to a list in a service when the directive is created.
It is important that this happens in the order that the selectable directives appear in the view.
From my research:
Directive compilation step by step and other
I thought that this would work because pre-link and controllers would be created in order. But this does not always happen.
Here is my HTML:
<selectable ng-repeat="suggestion in pCtrl.suggestions" value="suggestion">
{{suggestion}}
</selectable>
<div ng-repeat="cat in pCtrl.categories">
<selectable ng-repeat="item in cat" value="item">
{{item}}
</selectable>
</div>
<selectable ng-if="true" value="pCtrl.bottom">
<div>
Bot content
</div>
</selectable>
And here the directive:
app.directive('selectable', function() {
return {
restrict: 'E',
replace: true,
scope: {},
controller: 'SelectableCtrl',
controllerAs: 'selCtrl',
bindToController: {
value: '='
}
};
})
.controller('SelectableCtrl', [
function() {
var self = this;
console.log(this.value);
}
]);
What I see in the console log is that the bottom selectable with the ng-if is created just after the first ng-repeat and then the rest of the ng-repeats are created.
I made a plunker to demonstrate what happens. Please check the console log of the plunker.
Plunker: Directive creation order test

In angular JS Compilation order of nested directives based on priority
the deeper the element nested, the later it is compiled.
In your code
selectable for the Categories are nested in the div
div ng-repeat="cat in pCtrl.categories"
first the selectable directives which are present in the outer div tag gets compiled
and then remaining selectable directive which is present in inner div tag gets compiled.
Hence the console output showing the order as per its compilation.

Related

Angular - Directive is rendered first before variable evaluates?

I have this directive for showing all photos with a specific category:
<div ng-controller="CategoryController as c">
<photo-set category="{{ c.category_name }}"></photo-set>
</div>
Strangely, the template is rendered first before the variable sets in. So, it thought the attribute is empty. If I hard-code the attribute like <photo-set category="animal"> then it works fine.
There's no typo in the code, because when I inspect element, I can see the variable printed there.
.directive("photoSet", function() {
return {
restrict: "E",
templateUrl: "views/photo-set.html",
scope: {
category: "#category"
},
controller: "SetController", // the controller to render the photos
controllerAs: "s"
};
})
My Category Controller:
.controller("CategoryController", function($routeParams) {
this.category_name = $routeParams.category;
})
// the route is /c/:category
Edit: The JSFiddle. In the fiddle, It can't work using scope: { category: "#category" }, so I replace it with scope: true
it is very much dependent on your template code. Like, there is no 'link' or 'compile' function, so it is not clear how you are using the category_name inside your template. Try passing it as object using '=' instead '#' expression and use it through scope only. And for faster help try posting sample fiddle
EDIT:
Like i said above, your 'directive template code' should use the variables set in directive scope(in your case your directive tag is different and <ul> tag is different this <ul> tag should be part of your directive template):
<photo-set category="c.category_name">
<ul>
<li ng-repeat="photo in s.photos">{{ photo.src }}</li>
</ul>
</photo-set>
should be
<photo-set category="{{category_name}}"></photo-set>
and <ul> should be part of template
please checkout the working updated fiddle:
https://jsfiddle.net/Lp404d99/5/
more details about the execution flow
http://jasonmore.net/angular-js-directives-difference-controller-link/

AngularJs DOM manipulation in directive

I have a directive which shows a list of Users with their names as links.
Inside the the template of this directive I have following loop:
<ng-repeat="user in myctrl.users />
<a href="" >{{user.name}}</a>
Now I want to add an attribute directive to all the anchor tags with
with name edit-confirm-popup as shown below.
<ng-repeat="user in myctrl.users />
<a href="" edit-confirm-popup>{{user.name}}</a>
What I want to do is whenever user click on link of user's name I want to show a popup with popup's html inserted as sibling of the anchor element. I don't want to repeat the popup html when directive is compiled rather I want to insert it dynamically when user clicks on the link.
I am able to achieve following things :
1) attaching click event listener on directive element that is anchor element in directive's link function.
Don't know
i) how I should insert the template as I want to?
ii) I want the current user to be available in event listener so that I can check some conditions before I show the popup.
Here is my directive code :
function editConfirmPopup() {
function linkFunction(scope, iElement, iAttrs){
console.log(iElement);
iElement.on('click',function onEditUser(e){
console.log(e);
console.log('in event handler');
});
}
var directiveDefinitionObject = {
restrict: 'A',
scope: {
user: '=',
populateUser : '&'
},
controller: 'UserEditConfirmController',
controllerAs: 'userEdit',
link : linkFunction,
bindToController: true
};
return directiveDefinitionObject;
}
angular
.module('mymodule')
.directive('editConfirmPopup', editConfirmPopup);
ng-if directive conditionally appends (or removes) - not just shows/hides - the elements on which it operates.
Using the fact that ng-repeat creates a child scope for each iteration, you can then just use a simple toggle variable to decide whether to show/hide the additional HTML (that you call "popup"):
<div ng-repeat="user in myctrl.users">
<a ng-click="showPopup = !showPopup">{{user.name}}</a>
<div ng-if="showPopup">
The "popup" HTML
</div>
</div>

ng-repeat render order reversed by ng-if

I've just come across this and am wondering if it's a bug or expected behaviour? This is just a small example to show the issue. The code below is used in both examples:
<body ng-app="app" ng-controller="AppCtrl">
<item-directive ng-repeat="item in ::items" item="item"></item-directive>
</body>
angular
.module('app', [])
.controller('AppCtrl', AppCtrl)
.directive('itemDirective', itemDirective)
.factory('model', model);
function AppCtrl($scope, model) {
$scope.items = model.getItems();
}
function itemDirective() {
return {
restrict: 'E',
replace: true,
scope: {
item: '='
},
templateUrl: 'item-directive.html'
};
}
function model() {
return {
getItems: getItems
}
function getItems() {
return [
{
type: 'test',
title: 'test 1'
},
{
type: 'test',
title: 'test 2'
},
{
type: 'test',
title: 'test 3'
}
];
}
}
The first example has this item-directive.html which gets rendered in the correct order as expected
<div>
<span>{{::item.title}}</span>
</div>
Plunkr without ng-if
But the second example - which has the below item-directive.html - incorporates an ng-if, which is causing the list to get rendered in reverse order?
<div ng-if="item.type == 'test'">
<span>{{::item.title}}</span>
</div>
Plunkr with ng-if
-------- UPDATE ----------
I've just noticed (which relates to the issue noted in #squiroid's answer) that the isolate scope isn't actually working in this example. It appears to be, but item is being made available to the item-directive scope (or rather the scope it ends up with) by the ng-repeat, not the isolate scope. If you try to set any other values on the isolate scope, even though they show up on the scope passed to the directive's link and controller functions (as can be seen in the console output for the plnkr), they're not available to the template. Unless you remove replace.
Plunkr showing broken isolate scope
Plunkr showing fixed isolate scope when replace:false
--- UPDATE 2 ---
I've updated both of the examples to show the issue persisting once the the isolate scope is removed
Plunkr without ng-if and no isolate scope
Plunkr with ng-if and no isolate scope
And also a new version showing the change from templateUrl to template - as suggested by #Manube - that shows the behaviour working as expected
Plunkr with ng-if and no isolate scope using template instead of templateUrl
Using ng-if on a root element of a directive with replace: true creates a broken scope
ISSUE
This is happening with the combination of replace:'true' and ng-if on the root element.
Make sure the contents of your html in the templateUrl has exactly one root element.
If you place ng-if on span
<div >
<span ng-if="item.type == 'test'">{{::item.title}}</span>
</div>
Now why it is happening,it is happening because The ngIf directive removes or recreates a portion of the DOM tree based on an {expression}. If the expression assigned to ngIf evaluates to a false value then the element is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
which may lead to no root element on the templateUrl while rendering and thus leads to unwanted behaviour.
It is to do with templateUrl, which is asynchronous;
If you replace templateUrl by
template:'<div ng-if="item.type === \'test\'"><span>{{::item.title}}</span></div>'
it will work as expected: see plunker with template instead of templateUrl
the test <div ng-if="item.type === 'test'"> will execute when scope is ready and the templateUrl has been fetched.
As the way the template is fetched is asynchronous, whichever template comes back first executes the test, and displays the item.
Now the question is: why is it always the last template that comes back first?
second one showing in reverse order due to custom directive defined with item-directive name but its not rendering into DOM due to replace=true is used.
for more ref you can refer to this link

ng-repeat not binding when directive template is loaded via templateUrl

I've been stuck on this for hours and hours - can anyone help?
I have a list of nested directives, which I'm iterating through ng-repeat. The templates for these directives are fairly chunky so I've modularised them into separate HTML files and loaded them via templateUrl, but this seems to be breaking the data binding.
I've replicated the problem here: http://plnkr.co/edit/72HUb0vhtpYWuRHnlq3b?p=preview
HTML:
<div project-ext ng-repeat="project in projects"></div>
project.html
{{project.name}} <button ng-click="projects.splice($index,1)">-</button><br>
<div schedule-ext ng-repeat="schedule in project.schedules"></div>
schedule.html
{{schedule.name}}<button ng-click="remove($index)">-</button>
JS:
app.directive('projectExt', function() {
return {
templateUrl: 'project.html'
};
});
app.directive('scheduleExt', function() {
return {
templateUrl: 'schedule.html',
link: function(scope) {
scope.remove = function(i) {
scope.$parent.project.schedules.splice(i,1)
};
}
};
});
Can anyone tell me why the remove buttons don't work in the second listing, when all I've done is change the directives construction from template to templateUrl?
This problem seems to be related to a bug reported at https://github.com/angular/angular.js/issues/2151
To workaround it, simply don't put ngRepeat and your directives which are using templateUrl on the same element; instead, place ngRepeat on an wrapper:
HTML:
<div ng-repeat="project in projects"><div project-ext></div></div>
project.html
{{project.name}} <button ng-click="projects.splice($index,1)">-</button><br>
<div ng-repeat="schedule in project.schedules"><div schedule-ext></div></div>
Plunk: http://plnkr.co/edit/BapWX0LpqkcLFegq1fhU
It is a known bug, please follow these links. You can use the other version you wrote as a workaround.
Transcluded element not being deleted on removal of list item when using ng-repeat
https://github.com/angular/angular.js/issues/2151
https://groups.google.com/forum/#!msg/angular/0CP0zpTnZMM/5OzBni7d9sgJ

AngularJS : directives nested in a ng-repeat

I have an angularjs web app which has a view with the following basic structure :
<ul>
<li ng-repeat="entry in entries" ng-switch on="entry.type" logic-options >
<div ng-switch-when=1 text-entry></div>
<div ng-switch-when=2 other-entry></div>
</li>
</ul>
Here text-entry and other-entry are directives which have there own templates and logic-options is another directive which has a template and controller.
The logic-options directive provides some functions to be used by the child scope and has replace: false so that it should append its template to the end of the li. The text-entry and other-entry directives simply have some templates to be inserted which are also used on other views.
When I run this the logic-options directive will render and seems to function but the inner directives (text-entry and other-entry) will not.
In the console I get the error :
Error: Argument '?' is required qa#...
What causes this error and how do I correct it?
A fiddle demonstrating this problem : http://jsfiddle.net/cubicleWar/c3mTT/1/
I believe you should be transcluding the child elems, also you may want to avoid
putting 'ng-switch' directive in a different layer, so that it would not conflict with
other directives.
app.directive('logicOption', function() {
return {
replace: false,
transclude: true,
template: "<div ng-transclude>{{entry.type}} : This is the logic stuff</div>"
};
});
http://jsfiddle.net/YusCU/

Resources