I'm learning how to create directives because they seem very useful and I thought it would be a good use for a top navigation bar. I'm not sure if I'm misunderstanding how they should be used, missed something small along the way or something entirely different.
The templateUrl isn't loading and looking through other posts and the docs, I can't see what went wrong.
Here is the directive
.directive('stNavDir', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'partials/TopNav.html',
scope: {
siteName: "=",
},
link: function(scope, element, attrbiutes) {
element.addClass('topBar');
}
}
Using it in index.html
<body>
<st-NavDir site-name={{siteName}}>{{title}}</st-NavDir>
TopNav.html
<div>
<button>Menu</button>
</br>
<div >
This will hold the navigation with a menu button, title of current location in app, and maybe other things
</div>
</div>
So it only shows the value of {{title}} and, looking in the console, there are no errors and it doesn't seem to even load TopNav.html.
If I'm using it completely wrong or there's a better way, I'm all ears as well. But this seemed like a good place to try out using a directive. I can load it fine using ng-include but I wanted to try this way and see if it would be more effective.
I'm also having trouble getting the style to take but that may be caused by this initial problem.
Change this line
<st-NavDir site-name={{siteName}}>{{title}}</st-NavDir>
to
<st-nav-dir site-name={{siteName}}>{{title}}</st-nav-dir>
Camel-case should be converted to snake-case.
st-nav-dir
in html may help.
stNavDir is the corresponding directive definition name.
Here is an interesting article:
Naming a directive
Related
I know that a angular directive can be define in four ways:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
'M' - only matches comment
For example a directive with the restiction "M":
angular.module('exampleApp', [])
.directive('myDirective', function() {
return {
restrict: 'M',
...
};
});
and declaring the directive in HTML
<!-- directive: my-directive -->
But why would anybody use the M restriction for a directive? I find this really strange. Because if i comment out code, i don't want it to run. So why is this a thing?
From the docs:
Best Practice: Prefer using directives via tag name and attributes
over comment and class names. Doing so generally makes it easier to
determine what directives a given element matches.
Best Practice: Comment directives were commonly used in places where the DOM API limits the ability to create directives that spanned multiple elements
(e.g. inside elements). AngularJS 1.2 introduces
ng-repeat-start and ng-repeat-end as a better solution to this
problem. Developers are encouraged to use this over custom comment
directives when possible.
It is not good practice to use it now.
According to this post it is used usually only for backwards compatibility and for passing markup validations.
An example of how to use it is below
(function() {
angular
.module('exampleApp', [])
.directive("comment", function() {
return {
restrict: 'M',
replace : true,
template : "<h1>Made by a comment directive!</h1>",
link: function(scope, element, attrs) {
console.log(attrs.comment);
}
};
})
})();
<!DOCTYPE html>
<html ng-app='exampleApp'>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
</head>
<body>
</body>
<body>
<!-- directive: comment comment argument string -->
</body>
</html>
Because if i comment out code, i don't want it to run.
This is not HTML you're talking about, it's an Angular convention/feature. The comment is still visible/accessible inside the DOM as a comment node. The HTML parser won't do anything with it, true. But Angular can still find and parse and act on it. So, yes, it's another way to declare a directive which doesn't have any side effect on the DOM. You have to very explicitly use the directive: ... syntax inside a comment; you probably won't be triggering this by accident with code you simply want to comment out.
I can't seem to get this done: I have HTML that is compiled from a ng-repeat, and I'd like to compile the result of that as well. How would I go about that?
I have a dataset containing chunks of text, that have been given a display type. This type is set as the span class. Most types are just triggering CSS rules (for example, comment-style boxes, see screenshot), but others should invoke a directive.
For example, the chunk containing 'named Nicodemus, ' is of type-hidden. I have a directive that collapses the chunk and inserts a little button to expand it.
Code:
<span class="chunk type-{{chunk.type}}" ng-repeat="chunk in verse.chunks">{{chunk.text}}</span>
Results in something like
<span class="chunk type-hidden">named Nicodemus, </span>
If the second would be my source html, it would compile the typeHidden directive just fine. I guess I need to find a way to make angular compile a second time. I can't seem to get it done using $compile (though I guess I don't really understand how that works).
Hope you can help!
Thanks in advance!
Here's a plunker to show how you can get a directive to compile an element and then again.
The code for the lazy:
angular
.module('App')
.directive('compileTwice', compileTwiceFactory);
function compileTwiceFactory($compile) {
return {
restrict: 'AE', // Whatever you want
terminal: true, // Angular should not keep compiling the element
// by itself after encountering this directive!
compile: compile, // Instead, we tell Angular how to compile the rest of the element
priority: 1001, // This directive should get compiled before the others, obviously
};
function compile(element, attrs) {
element.removeAttr('compile-twice');
element.removeAttr('data-compile-twice');
return function postLink(scope, _element, _attrs) {
var compiledTwice = $compile($compile(_element)(scope)[0])(scope)[0];
// do something with compiledTwice
};
}
}
edit: And obviously you can generalize that to compile an arbitrary number of times that you could specify like this:
<div compile-n-times="420"></div>
edit: The plunker doesn't seem to work under Firefox?
I've actually been able to fix this with a workaround. Not as elegant, but it works if I nest my directive within the ngrepeat and hardcode the name, making it visible using ng-if.
<!-- special type hidden -->
<span ng-if="chunk.type=='hidden'">
<span class="type-hidden">
{{chunk.text}}
</span>
</span>
I have built an app where I need dynamically change templates.
<div ng-include="templateUrl"></div>
I have two templates like addtext.html and addmedia.html. I dynnamically change it using angularjs. But in my addmedia.html template, I have filepicker's pick file widget.
<input type="filepicker" data-fp-apikey="..." data-fp-mimetypes="*/*" data-fp-container="modal" onchange="save_url()">
The pick widget does not load in the template. Is there any way to get it to work?
Well, I don't know much about filepicker.io, but as far as I understand, the problem is that widgets are constructed on page load. This means that when you switch templates, filepicker may not process them automatically, see documentation:
We'll automatically convert any widgets that you have on page load. However, if you want to create widgets once the page has loaded, you can... call filepicker.constructWidget(); method.
So, I would suggest you a simple directive that just puts an input tag with type=filepicker, and then linking filepicker to it using before mentioned method, like this:
app.directive('myFilepicker', [function() {
return {
transclude: true,
restrict: "E",
template: '<input type="filepicker">',
replace: 'true',
link: function(scope, element) {
filepicker.constructWidget(element);
}
};
}]);
and in your markup you can use it like this:
<my-filepicker data-fp-apikey="..." data-fp-mimetypes="*/*" data-fp-container="modal" onchange="save_url()"></my-filepicker>
See this demo. I don't have filepicker.io API key, but I hope this would work.
I am relatively new to AngularJS.
I have a series of DIVs in a partial view. Each of the DIVs has a unique ID. I want to show / hide these DIVs based on a scope value (that matches one of the unique ID).
I can successfully write out the scope value in the view using something like {{showdivwithid}}
What would be the cleanest way to hide all the sibling divs that dont have an ID of {{showdivwithid}}
I think you are approaching the problem with a jQuery mindset.
Easiest solution is to not use the id of each div and use ngIf.
<div ng-if="showdivwithid==='firstDiv'">content here</div>
<div ng-if="showdivwithid==='secondDiv'">content here</div>
<div ng-if="showdivwithid==='thirdDiv'">content here</div>
If you don't mind the other elements to appear in the DOM, you can replace ng-if with ng-show.
Alternatively use a little directive like this:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {},
template: '<div ng-transclude></div>',
link: function (scope, element, atts) {
if(atts.id != atts.keepIfId){
element.remove();
}
}
};
});
HTML
<div id="el1" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el2" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el3" keep-if-id="{{showdivwithid}}">content here</div>
First, I want to echo #david004's answer, this is almost certainly not the correct way to solve an AngularJS problem. You can think of it this way: you are trying to make decisions on what to show based on something in the view (the id of an element), rather than the model, as Angular encourages as an MVC framework.
However, if you disagree and believe you have a legitimate use case for this functionality, then there is a way to do this that will work even if you change the id that you wish to view. The limitation with #david004's approach is that unless showdivwithid is set by the time the directive's link function runs, it won't work. And if the property on the scope changes later, the DOM will not update at all correctly.
So here is a similar but different directive approach that will give you conditional hiding of an element based on its id, and will update if the keep-if-id attribute value changes:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {
keepIfId: '#'
},
template: '<div ng-transclude ng-if="idMatches"></div>',
link: function (scope, element, atts) {
scope.idMatches = false;
scope.$watch('keepIfId', function (id) {
scope.idMatches = atts.id === id;
});
}
};
});
Here is the Plunkr to see it in action.
Update: Why your directives aren't working
As mentioned in the comments on #david004's answer, you are definitely doing things in the wrong way (for AngularJS) by trying to create your article markup in blog.js using jQuery. You should instead be querying for the XML data in BlogController and populating a property on the scope with the results (in JSON/JS format) as an array. Then you use ng-repeat in your markup to repeat the markup for each item in the array.
However, if you must just "get it working", and with full knowledge that you are doing a hacky thing, and that the people who have to maintain your code may hate you for it, then know the following: AngularJS directives do not work until the markup is compiled (using the $compile service).
Compilation happens automatically for you if you use AngularJS the expected, correct way. For example, when using ng-view, after it loads the HTML for the view, it compiles it.
But since you are going "behind Angular's back" and adding DOM without telling it, it has no idea it needs to compile your new markup.
However, you can tell it to do so in your jQuery code (again, if you must).
First, get a reference to the $compile service from the AngularJS dependency injector, $injector:
var $compile = angular.element(document.body).injector().get('$compile');
Next, get the correct scope for the place in the DOM where you are adding these nodes:
var scope = angular.element('.blog-main').scope();
Finally, call $compile for each item, passing in the item markup and the scope:
var compiledNode = $compile(itm)(scope);
This gives you back a compiled node that you should be able to insert into the DOM correctly:
$('.blog-main').append(compiledNode);
Note: I am not 100% sure you can compile before inserting into the DOM like this.
So your final $.each() in blog.js should be something like:
var $compile = angular.element(document.body).injector().get('$compile'),
scope = angular.element('.blog-main').scope();
$.each(items, function(idx, itm) {
var compiledNode = $compile(itm)(scope);
$('.blog-main').append(compiledNode);
compiledNode.readmore();
});
I am new to AngularJS, and in learning stage, i have come across ngTransclude, when i start reading the document http://docs.angularjs.org/api/ng/directive/ngTransclude, i could able to get , what that really does,
Can any of you guys, please tell me what actually ngTransclude does, And what is the change when it is included, when when it is not included in directive.
Please give me a clear understanding of ngTransclude, and how important is, and when we can use it.
I Just Want To See It Working:
http://plnkr.co/edit/liRDIEy9sWoSPSHJ1oln?p=preview
Explanation Of The Example
Think of "ngTransclude" as saying "keep whatever is inside of this element, inside of the element, even after I rewrite it". It is most used with directives. Take the following as an example, I defined a directive "emphasize-text" which really just is going to wrap whatever I am provided in an "h1" element. The following is that directive:
app.directive("emphasizeText", function () {
return {
restrict: 'E',
transclude: true,
template: '<h1 ng-transclude></h1>',
}
});
Now, to use this on a page, the following html will work quite well:
<!-- transclude will keep text of course -->
<emphasize-text>Example With Just Text</emphasize-text>
<!-- transclude also keep html tags intact -->
<emphasize-text><i>Example With Italics Tag</i></emphasize-text>
<!-- translcude will even keep angular variables intact! -->
<emphasize-text>
<div>This is a first div</div>
<div>This is a {{secondVariable}} div</div>
</emphasize-text>
If you take a look at this plunker (http://plnkr.co/edit/liRDIEy9sWoSPSHJ1oln?p=preview). Use a modern browser and "view source" on the output and you'll see what is happening. The tags are maintained exactly, angular just wraps them in "h1"! Perfectly what we wanted!
The key here is you can modify the semantics, behavior or really anything you want in html! Personally, I feel this is best suited for custom controls - not for something simple like I used it for. In fact, my example is probably a really bad one because future programmers would know what "h1" is much faster than they will know what "emphasize-text" will turn into. That said, you get the idea.
Happy customizing!