So I am basically creating a table with nested ng-repeat.
<div ng-controller="PresetManageController">
<table>
<in-preset ng-repeat="preset in presetManage.presets"></in-preset>
</table>
</div>
And the template of directive in-preset is
<tr>
<td>{{preset.name}}</td>
<td>一些属性</td>
<td>
编辑
删除
</td>
</tr>
Directive declaration
module.directive('inPreset', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'scripts/directive-templates/in-preset.html'
}
});
But when I open the page, I found that all trs jumped outside of the table.
If I change table to div, then it works fine. I have checked all html tags are perfectly closed. It is damn hard to debug this issue. Any clues?
The issue is because you are using ng-repeat on a custom directive (set to replace). There are a few weird behaviors with ng-repeat and replacing directives (see related post).
Try to leave the <tr> in your html and change your directive to be on an attribute instead.
html
<table>
<tr in-preset ng-repeat="preset in presets"></tr>
</table>
js
.directive('inPreset', function() {
return {
restrict: 'A',
template: '<td>{{ preset.name }}</td>',
};
})
See the Fiddler
Related
I am wondering, is it possible to pass an $index param into the ng-transclude? I am trying to focus into the textarea by clicking on the elements in the ng-transclude, triggering a function inside the controller that sees the textarea, but I can't get the right ID.
<div
ng-repeat="locale in $ctrl.enabled">
<table>
<tbody>
<tr>
<td flex layout="row" layout-align="start end">
<ng-transclude
ng-transclude-slot="theExtraMenu">
</ng-transclude>
</td>
</tr>
<tr>
<td>
<md-input-container
md-no-float="true">
<textarea id="{{'textarea'+$index}}">
</textarea>
</md-input-container>
</td>
</tr>
</tbody>
</table>
</div>
So in the end, I created a parent component that has the locale as an input and transcludes all of the necessary content. Then I require the controller of said component and that's how I see the value of locale.
The reason I didn't go with the scope parent access was that if the hierarchy of the scope changed, it got broken.
Thanks #georgeawg for inspiration.
EDIT: example -
<div ng-repeat="locale in $ctrl.enabled">
<translated-textarea-menu locale="locale">
<the-extra-menu>
<ng-transclude ng-transclude-slot="theExtraMenu"></ng-transclude>
</the-extra-menu>
</translated-textarea-menu>
The parent component (translated-textarea-menu)
component: {
templateUrl: 'xyz.html',
bindings: {
locale: '&',
},
transclude: {
theExtraMenu: '?theExtraMenu'
},
controller: TranslatedTextareaMenuController,
controllerAs: '$ctrl',
}
And then in the transcluded component (the extra menu component)
component: {
templateUrl: 'x.html',
bindings: {
variables: '&'
},
require: {
translatedTextareaMenu: '^translatedTextareaMenu'
},
controller: TheExtraMenuController,
controllerAs: '$ctrl',
}
And access it with this.translatedTextareaMenu.locale
Directives can require the controllers of other directives to enable communication between each other. This can be achieved in a component by providing an object mapping for the require property. The object keys specify the property names under which the required controllers (object values) will be bound to the requiring component's controller.
For more information, see
AngularJS Developer Guide - Intercomponent Communication
AngularJS Comprehensive Directive API Reference - require
I'm creating a directive with the following template (simplified):
<table>
<tr>
<td>
<input type="text"/>
</td>
<td>
<input type="text"/>
</td>
</tr>
</table>
In the directive's link function I want to add listeners to the inputs, but I can't get the input elements. The directive looks like the following:
angular.module('app').directive('myDirective', function(){
return{
restrict: 'E',
templateUrl: '<path-to-above-html-file>',
link: function(scope, element, attr){
var inputs = element.find('input'); // Returning empty JQLite object
}
};
});
According to the Angular element documentation, the find() method should be able to find nested elements. But why does this not work then?
I've tried to print the element in the console and loop trough all child elements, and the inputs do exist.
Any help is appreciated!
If you want to access element,then use querySelector().But beware,querySelector only returns first child element.
If you want to get all elements use querySelctorAll() like below
element.querySelectorAll("input")
I've been looking all over the internet for something like this and I still can't find the answer.
I have a directive that is reused throughout my application, it allows the user to sort and search through lists of items. I have multiple kinds of items that can be passed in to the directive (html templates that I pass in) as well as multiple uses for those templates. I.e, sometimes I want a certain button on that template, but sometimes I want another. (This will make more sense in a minute).
Therefore I have created multiple directives with transclusion in order to achieve this. However, I'm having serious issues with scoping and I can't seem to figure out exactly how to pass the isolated scope to the child directive.
Below is my code:
Item List Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function (scope, element, attributes, ctrl, transclude) {
//Things that modify the scope here. This scope is what I want to pass down to the child directives
//NOTE: I do not currently have a working transclude function which is why I didn't include it here because I have no idea what to do with it
scope.pagedItems = groupItemsToPages(items);
scope.currentPage = 0;
};
return {
restrict: 'E',
replace: 'true',
transclude: true,
templateUrl: 'partials/directives/item-list.html',
link: linkFunction,
scope: {
searchPlaceholder: "#",
items: "=",
control: "="
}
};
});
item-list.html
<div class="form-group">
<!-- I won't put all of the html here, just some to show you what i'm going for -->
<div class="search-field">
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}/>
</div>
<table class="table table-hover">
<tbody>
<tr ng-repeat="item in pagedItems[currentPage]">
<td ng-transclude></td>
</tr>
</tbody>
</table>
</div>
Here's the directive that simply returns the URL of whatever template is passed to it. This is so that I can add in an extra html through further nested transclusions.
item-template.js
var app = angular.module('main');
app.directive('itemTemplate', function() {
return {
restrict: 'AE',
replace: 'true',
transclude: true,
templateUrl: function(tElement, tAttrs){
return tAttrs.templateUrl;
}
};
});
Here's an example template (extremely simplified again, just to show you the layout)
profile-template.html
<div>
<p>item.name</p>
<p>item.description</p>
</div>
<div ng-transclude></div>
Here's an example of the HTML that calls this code
tab.html
<div class="tab">
<div class="available-items">
<item-list control="profileControl" search-placeholder="Search Profiles" items="profileControl.profiles">
<item-template template-url="partials/profile-template.html">
<button type="button" ng-click="launchProfile(item.id)">Launch Profile</button>
</item-template>
</item-list>
</div>
</div>
So now that you've seen the code. The issue I'm having is that my profile-template.html inclusion isn't getting the scope from the directive above it even though I've tried cloning the scope to it. All the examples I've seen require you to remove the template key from the directive and assume you're only returning the code you have in your transclusion. In my case, I have other html that I want to display in the template.
Any help would be appreciated
Instead of trying to pass the scope between your directives, you can make use of the $parent attribute and get access to higher scopes.
For instance, in your item-template controller you could gain access to the higher scope from item-list with a code like this:
if ($parent.searchPlaceholder === '') {
...
}
im very new to AngularJS and now I wrote my first directive.
The directive represents a table row and the markuplooks like the following:
<table>
<!--thead ...-->
<tbody>
<my-directive ng-repeat="(key, value) in object"></my-directive>
</tbody>
</table>
But the directive is renderd / printed out before the sorrounding table tags.
So my DOM looks like this:
<!-- multiple times -->
<my-directive></my-directive>
<table>...</table>
Heres my directive definition
return {
restrict: "E",
templateUrl: "/my-directive.html",
link: function(scope, element, attrs){
}
};
So why does this happen?
Other than tr, td, tbody, th elements never gonna work inside the table element. You should really use attribute directive so that it can support inside table to by doing restrict: "A", in directive.
<tbody my-directive ng-repeat="(key, value) in object">
</tbody>
If you want to have the other than table element to be there, then they could be placed inside th & td
Kind of going along with what Pankaj said, you can't call the directive from inside the table, its invalid HTML.
However, I can understand why you would want to use restrict: 'E'. Personally, I like using my directives as elements.
So, if you want to still use the directive as an HTML element The simplest fix to this would be to have your directive itself render the whole table. It could be a <table-for/> directive, or something like that.
This has been my workaround for this problem. Markup is markup at the end of the day, and you still have to play by the rules.
So I came to the following approach:
<tbody>
<tr my-directive ng-repeat="..."></tr>
</tbody>
And I restricted my directive to argument and it works. Thank you for your help.
I'm trying to implement custom sortBy directive in order to make columns in html table sortable.
HTML:
<thead>
<tr>
<sort-by-directive
ng-repeat="header in headers"
onsort="onSort"
sortdir="filterCriteria.sortDir"
sortedby="filterCriteria.sortedBy"
sortvalue="{{ header.value }}">{{ header.title }}
</sort-by-directive>
</tr>
</thead>
JS:
angular.module('mainApp.directives').directive('sortByDirective', function () {
return {
templateUrl: 'SortHeaderTemplate',
restrict: 'E',
transclude: true,
replace: true,
scope: {
sortdir: '=',
sortedby: '=',
sortvalue: '#',
onsort: '='
},
link: function (scope, element, attrs) {
scope.sort = function () {
if (scope.sortedby == scope.sortvalue)
scope.sortdir = scope.sortdir == 'asc' ? 'desc' : 'asc';
else {
scope.sortedby = scope.sortvalue;
scope.sortdir = 'asc';
}
scope.onsort(scope.sortedby, scope.sortdir);
}
}
};
});
Directive Template:
<script id="SortHeaderTemplate" type="text/ng-template">
<th ng-click="sort(sortvalue)">
<span ng-transclude=""></span>
<span ng-show="sortedby == sortvalue">
<i ng-class="{true: 'sorting_asc', false: 'sorting_desc'}[sortdir == 'asc']"></i>
</span>
<span ng-show="sortedby != sortvalue">
<i ng-class="{true: 'sorting', false: 'sorting'}[sortdir == 'asc']"></i>
</span>
</th>
</script>
So when I use th as root tag of directive template I retrieve an error:
Error: [$compile:tplrt] Template for directive 'sortByDirective' must have exactly one root element. SortHeaderTemplate
but when I change th to a or span tags everything works fine.
What am I doing wrong?
I've encountered oddities like that with directive and table elements. See this issue for example. Try wrapping your template with div tag or use replace:false.
This isn't your case, but I had this very same issue because my code had html comments before and after the template markup, like so:
<!-- Foo Widget -->
<div class="foo-widget">[...]</div>
<!-- end:: Foo Widget -->
I got rid of the comments and voilá - problem solved.
I expect that the <th> is getting melted away at some intermediate point when it is evaluated outside the context of a <tr> (put that template into some random part of your webpage to see the <th> disappear).
In your position, I would use a <div> in the template, change sort-by-directive to an 'A' type directive, and use a <th sort-by-directive>...</th> as before, without replace: true.
This error can be also caused by the fact that you need to have a wrapping element for all your tags in the directive's template. Your directive's template can't be only:
<nav></nav>
<div></div>
It must be:
<div>
<nav></nav>
<div></div>
</div>
I got this error when I used the template property of the directive definition when I should've been using templateUrl if that helps anyone.
I know this is old, but there is another solution.
I encountered this issue also, and tried all the above solutions with no luck.
turns out, for some weird reason, that this error is thrown also in case there is a typo in the 'templateUrl' - if angular can't find the html file by the given path - you get the same 'must have exactly one root element' error.
so - fixing the templateUrl fixed the error for me.
hope this helps anyone in the future.
As stated by others: this is because your browser ignores the TH before it gets placed inside the table. My prefered way to fix this is to change the directive to an attribute directive and add it to a TH in the table.
Directive looks like this:
.directive('sortByDirective', function () {
return {
templateUrl: 'SortHeaderTemplate',
restrict: 'A',
transclude: true,
replace: false,
scope: {
sortdir: '=',
sortedby: '=',
sortvalue: '#',
onsort: '='
},
link: function (scope, element, attrs) {
scope.sort = function () {
if (scope.sortedby == scope.sortvalue)
scope.sortdir = scope.sortdir == 'asc' ? 'desc' : 'asc';
else {
scope.sortedby = scope.sortvalue;
scope.sortdir = 'asc';
}
scope.onsort(scope.sortedby, scope.sortdir);
}
}
};
});
Setting it on your page looks like this:
<th sort-by-directive
ng-repeat="header in headers"
onsort="onSort"
sortdir="filterCriteria.sortDir"
sortedby="filterCriteria.sortedBy"
sortvalue="{{ header.value }}">{{ header.title }}
</th>
I have encountered the issue couple of times and most of the times it could be that you are not wrapping your elements under one element like
<div>
<div... </div>
</div>
But there was one occasion where you get this error when the template path is not correct. So please check if you are referring to the template correctly.
A more specific case of Aaron's answer is when you think that you have the correct path to the template while you actually don't: as it's stated in the documentation for the templateUrl
The URL is relative to our index.html file
In my case, I have placed my templates directory one level below index.html. When I was setting my property to templateUrl: '../templates/some-form.html', the path was relative to the script but not to index.html, resulting to the same error.
i encounter the following error:
Error: [$compile:tplrt] http://errors.angularjs.org/1.2.6/$compile/tplrt?p0=stockWidget&p1=stock.html.
I get around by removing commement at very top of template file.
replace is deprecated with angularjs 1.3 forward, the next release will remove it completely, it's better not to use replace key.
Which version of angular are you using ?
There was a bug for something looking like your problem that was fixed in 1.2.13 1.3 Beta 1 commit link
https://github.com/angular/angular.js/issues/1459
I know it's very old answer and question but I encountered this error and fix it by put my comment inside a div tag.
before:
<!--You commented code-->
<div>
</div>
after:
<div>
<!--You commented code-->
</div>
Adding to Biljana's answer, when you think that you have the correct path to the template while you actually don't, specifically upper/lowercase differences in dos/mac/unix systems.
My local path:
templateUrl: 'ng/ImageDnd/dropzone.tpl.html',
my real path:
ng/ImageDnD/dropzone.tpl.html
notice the D at the end of AnnotationsDnd.
My Macbook does not complain, but got an error when deploying in Linux.