Angular - Directive is rendered first before variable evaluates? - angularjs

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/

Related

Pass rendered string into Angular directive

I am trying to make an angular directive that renders dynamic content urls based on an attribute placed on the directive. Example:
Directive:
angular
.module('myModule')
.directive('myContent', directive);
function directive() {
return {
replace: true,
templateUrl: function (elem, attrs) {
return attrs.contentUrl;
}
};
}
HTML:
<div my-content content-url="url/to/my-content.html"></div>
However what I would like is for the content-url attribute to be populated by a string from the controller. So let's say the controller is using the "controllerAs" syntax with the name "home", I would like the html to read:
<div my-content content-url="{{home.myContent.url}}"></div>
However within the directive's templateUrl function, the contentUrl attribute is being sent literally as "{{home.myContent.url}}". How can I get this value to evaluate before running the templateUrl function? Or, is there a better way to have simple, dynamic content available from a directive?
The answer is provided by #crhistian-ramirez:
Just use ng-include
<div ng-include="vm.myContent.url"></div>

Passing ng-repeat context to child directive

In parts of my application I have used a directive in this format:
<child-directive ng-repeat="item in vm.items"></child-directive>
This one has access to {{item}} from within child-directive without having to do anything.
Now I want to use the same directive along side other directives that all work with the same context data:
<div ng-repeat="item in vm.items">
<child-directive></child-directive>
<other-directive></other-directive>
</div>
The child directive does not need to alter the context data, it only needs the information from inside to display a widget.
I've tried using scope in the directive in this format:
angular
.module('myapp.dashboard')
.directive('childDirective', childDirective);
function childDirective() {
var directive = {
restrict: 'E',
templateUrl: 'client/components/child-directive.ng.html',
controller: 'ChildDirectiveController',
controllerAs: 'vm',
scope: {
item: '='
}
};
return directive;
}
and
<child-directive ng-attr-item="{{item}}"></child-directive>
Within the ng-repeat section. However that just throws an error.
I've also tried ng-bind with no luck.
Any suggestions?
You're using bi-directional scope binding rather than an interpolated property. You can read up on this more in the angular docs
Change your scope object to be:
scope: {
item: '#'
}
OR, change your template to:
<child-directive ng-attr-item="item"></child-directive>

In directive template, inside ng-repeat scope not working

I have a directive as follows,
angular.module("sample").directive("sampleDir", [
"$compile", function($compile) {
return {
restrict: "E",
replace: true,
scope: {
options: '=',
source: '='
},
templateUrl: "sample.html"
};
}
]);
sample.html
<div ng-repeat="row in source" class="{{$parent.options.Class}}">
<label value="{{row.label}}">{{row.label}}</label>
</div>
Here inside template "source" parameter i can use as such. However, "options" parameter need to be fetched from parent scope. If i am creating another div outside, then options parameter i can get as such.
I tried adding another div outside this ng-repeat div and tried to access "options" parameter is working fine.
<div class="{{options.Class}}">
<div ng-repeat="row in source">
<label value="{{row.label}}">{{row.label}}</label>
</div>
</div>
But why it is not working with ng-repeat div?
How can i remove $parent and use "options" parameter as such?
Please help,
Thanks.
It's a known issue. It's happening when you use replace: true and ngRepeat on the top element of the directive. So the ngRepeat is bound to the outer scope instead of the isolated scope.
You can resolve it by either not using replace or adding an element to the top of the directive.
I think the scope variable name "options" might be conflicting. Try changing the scope variable of the directive to something else.
For eg: Your scope definition can change to this
scope: {
sampleOptions: '=',
source: '='
},
in case you are stuck with any syntax related issue doing this, this article will be helpful

In a Directive, passing function arguments through to the html template

I am on day 2 of Angular and am trying to create a directive. The idea is that I have several images of quilts to display and I don't want to repeat the same HTML. Here's an index.html snippet showing a use of the new directive and two 'arguments' I'll need in the partial:
<ng-samplequilt imgPath="img/t_3x3_no_sashing/jpg"
text="This is a 3x3 quilt without sashing.">
</ng-samplequilt>
Here's the partial:
<div>
<img src="{{imgPath}}"/>
<div>
{{text}}
</div>
</div>
Finally, there's the Directive (which may or may not work):
.directive('SampleQuilt', ['imgPath','text',function(imgPath, text) {
return {
restrict: 'E',
templateUrl: 'partials/sample_quilt.html'
};
}])
So I'm clearly a little over my head. I've read a good deal of the docs and some examples, but none seem to be quite what I'm doing. Or perhaps I have not internalized enough for it to stick.
I'm not looking for a full solution here; I don't mind working through it. But I am stuck - I don't know how to get imgPath and text to make their way to the partial where they can be used.
Also, Directives have embedded controllers. How does the partial know to refer to this controller? Why does it even have access to it, given it's buried in the Directive?
Thanks for a boot in the right direction.
EDIT -
Thanks to #Dalorzo I seem to have a solution.
First, his idea about defining the scope in the Directive worked.
Second, I named the directive "SampleQuilt". This did not work - the directive did nothing/could not be found. When I renamed it to sampleQuilt, however, the internal name translation worked. For similar reasons, the HTML had to refer to img-path, not imgPath.
Here are the three files now.
The index.html snippet:
<sample-quilt img-path="img/t_3x3_no_sashing.jpg"
text="This is a 3x3 quilt without sashing.">
</sample-quilt>
The partial:
<div>
<img src="{{img-path}}"/>
<div>
{{text}}
</div>
</div>
The directive:
.directive('sampleQuilt', function() {
return {
restrict: 'E',
scope:{ imgPath: "#", text: "#" },
templateUrl: 'partials/sample_quilt.html'
};
})
;
EDIT 2 -
The above doesn't work - I was getting burned by browser caching.
It seems as if this snippet in index.html is curious...
<sample-quilt img-path="img/t_3x3_no_sashing.jpg"
text="This is a 3x3 quilt without sashing.">
</sample-quilt>
The img-path attribute can apparently be spelled three different ways: img-path, 'imgPath', and img_path. All are converted to imgPath internally. When displaying the value in the partial, imgPath is correct.
Here's the corrected partial:
<div>
<img src="{{imgPath}}"/>
<div>
{{text}}
</div>
</div>
Based on your example above I think this should be what you intent:
var app = angular.module('demo',[]);
app.directive('SampleQuilt', function() {
return {
restrict: 'E',
scope:{ imgPath: "#", text: "#" },
templateUrl: 'partials/sample_quilt.html'
};
});
By adding scope to the directive we create an "isolated scope". With this approach scope can capture attributes in 3 ways:
# Captures the attribute value from the DOM as string value.
= Evaluates the attribute as property of the parent scope.
& Evaluates the attribute as method of the parent scope.
You can read more about it here:
http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
Regarding your html:
Remove ng and don't use it as part of your directives they are reserved by the angular team and it is good to avoid them to avoid conflicts. You can read more about Angular Naming Conventions here
Cases (camel case or pascal case) means dash in angular directives so SampleQuilt needs to be used as sample-quilt in the html.
Sample:
<sample-quilt imgPath="img/t_3x3_no_sashing/jpg"
text="This is a 3x3 quilt without sashing.">
</sample-quilt>
Regarding your last question about the controller on directives. Directives returned object has a controller property that you can use like:
app.directive('SampleQuilt', function() {
return {
restrict: 'E',
controller: 'myDirController', /* <--- Controller Declaration */
scope:{ imgPath: "#", text: "#" },
templateUrl: 'partials/sample_quilt.html'
};
});
app.controller('myDirController', ['$scope', function ($scope) {
// My Directive Controller implementation
}]);

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