AngularJS : directives nested in a ng-repeat - angularjs

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/

Related

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

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.

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/

How to get the attribute value in template html with angularjs

I wrote a directive but I don't know how to get the attribute of the directive in template html, those attributes are used in ngIf to determine which div elements will be displayed, pls take a look at below code snippet.
directive use:
<geo-country-selector type="WGC"></geo-country-selector>
template html:
<div ng-if="type.indexOf('W')>0">
...
</div>
<div ng-if="type.indexOf('G')>0">
...
</div>
I need to get the attribute type's value in the template html. I did lots of research on that, but no luck. Any ideas? Thanks
The directive can be like Anik said :
link : function(scope,element,attr){
...
},
scope: {
type: '#'
}
In your directive add a link function then add attrs value to scope then you can check condition in your template like that
try like this
link : function(scope,element,attr){
scope.type=attr.type
}
There is another way by creating isolate scope like this
scope: {
type: '#'
}

How to use the 'replace' feature for custom AngularJS directives?

Why does replace=true or replace=false not have any impact in the code below?
Why isn't the "some existing content" being displayed when replace=false?
Or putting it more humbly, can you kindly explain what is the replace=true/false feature in directives and how to use it?
Example
JS/Angular:
<script>
angular.module('scopes', [])
.controller('Ctrl', function($scope) {
$scope.title = "hello";
})
.directive('myDir', function() {
return {
restrict: 'E',
replace: true,
template: '<div>{{title}}</div>'
};
});
</script>
HTML:
<div ng-controller="Ctrl">
<my-dir><h3>some existing content</h3></my-dir>
</div>
See it in Plunker here:
http://plnkr.co/edit/4ywZGwfsKHLAoGL38vvW?p=preview
When you have replace: true you get the following piece of DOM:
<div ng-controller="Ctrl" class="ng-scope">
<div class="ng-binding">hello</div>
</div>
whereas, with replace: false you get this:
<div ng-controller="Ctrl" class="ng-scope">
<my-dir>
<div class="ng-binding">hello</div>
</my-dir>
</div>
So the replace property in directives refer to whether the element to which the directive is being applied (<my-dir> in that case) should remain (replace: false) and the directive's template should be appended as its child,
OR
the element to which the directive is being applied should be replaced (replace: true) by the directive's template.
In both cases the element's (to which the directive is being applied) children will be lost. If you wanted to perserve the element's original content/children you would have to translude it. The following directive would do it:
.directive('myDir', function() {
return {
restrict: 'E',
replace: false,
transclude: true,
template: '<div>{{title}}<div ng-transclude></div></div>'
};
});
In that case if in the directive's template you have an element (or elements) with attribute ng-transclude, its content will be replaced by the element's (to which the directive is being applied) original content.
See example of translusion http://plnkr.co/edit/2DJQydBjgwj9vExLn3Ik?p=preview
See this to read more about translusion.
replace:true is Deprecated
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636
Update
Note: replace: true is deprecated and not recommended to use, mainly due to the issues listed here. It has been completely removed in the new Angular.
Issues with replace: true
Attribute values are not merged
Directives are not deduplicated before compilation
transclude: element in the replace template root can have unexpected effects
For more information, see
AngularJS $compile Service API Reference - Issues with replace:true

Bind ngInclude to different models

Is it possible to specify model for ngInclude so that any changes done inside the included template are reflected on the specified model. For instance:
I have a model such as :
$scope.member = {
name: "Member1",
children:[
{
name:"Child1"
},
{
name:"Child2"
}
]
};
and the template:
<script type="text/ng-template" id="name.html">
<input type="text" ng-model="member.name"/>
</script>
Now is it possible to pass ngInclude either "member" or any child and get their respective name properties modified? I tried passing ng-model to the ng-template but it doesn't work. I tried to dynamically load the scope with the intended model but if the model gets delete, I am left with an orphan template. Following is the jsfiddle code:
http://jsfiddle.net/vaibhavgupta007/p7E5K/
I wish to reuse the template rather than duplicating the same template for different models. I have refered to this question:
How to specify model to a ngInclude directive in AngularJS?
But here models are not getting deleted.
Edit
I have not grasped the concepts of creating custom directives till now. But will creating a new directive in conjuction with ng-include help?
The answer of your last question is: yes. In a directive, you define also a template and a scope. The content of the scope is completely in your hands.
See here: http://jsfiddle.net/vgWQG/1/
Usage:
Member: <member model="member"></member>
<ul>
<li ng-repeat="child in member.children">
Child {{$index}}: <member model="child"></member>
</li>
</ul>
The directive:
app.directive('member', function(){
return {
template : '<input type="text" ng-model="member.name"/>',
replace : true,
restrict: 'E',
scope : {
'member' : '=model'
},
link: function(scope, element, attr) {}
};
});
I've moved the template in an inline variant because I could not getting the template cache getting to work in jsfiddle. In a real world, a templateUrl: 'name.html' should be fine.
This is what you want?

Resources