Why is my external template not rendering items using ng-repeat ?
--- AngularJS 1.1.5
I stumbled on this bug but I'm not sure if it's the same issue? If it is are there any work arounds?
Here's a Plunker using a static list but the solution needs to support two way binding because the data is dynamic. ( That's how it's supposed to work anyway... )
controller
.controller('main', ['$scope',function($scope) {
$scope.items = ['a','b','c'];
}])
directive
.directive('items', function() {
return {
restrict: 'E',
replace: true,
controller: 'main',
templateUrl: 'items.html',
link: function(scope, controller) {
}
};
})
items.html
<div ng-repeat="item in items">
{{item}}
</div>
This is because your directive uses a template that does not have on root element, try:
<div>
<h1>What?</h1>
<div ng-repeat="item in items">
{{item}}
</div>
</div>
for the template.
EDIT: To understand this, we can have a look at the source of angular itself. The compile-Methods responsible for this can be looked at here. The culprit is the mergeTemplateAttributes and the need for one root becomes apparent when looking at the intent here: When replacing DOM nodes, angular needs to pass on the attributes. When there is more than one node in the template fetched it will throw an error, as it cannot decide on the node which the original attributes will be applied to (see here for the error thrown).
The quote the developers here:
When the element is replaced with HTML template then the new on the template need to be merged with the existing attributes in the DOM. The desired effect is to have both of the attributes present.
Update your items.html as below:
<div>
<h1>What?</h1>
<div ng-repeat="item in items">
{{item}}
</div>
</div>
You need to wrap the template with one root like this:
<div>
<h1>What?</h1>
<div ng-repeat="item in items">
{{item}}
</div>
</div>
Related
Basically I want to achieve like this scenario:
https://i.stack.imgur.com/LLTcK.png
My research led me to $compile, $trustAsHtml,at last directive.
In $compile and $trustAsHtml I can only append static template or only html but can't use dynamic things such ui-sref, ng-click etc.
So, I tried to create directive it is not working and also I am unable to add multiple template on click.
controller :
app.controller('Ctrl', ['$rootScope', '$scope',function ($rootScope, $scope)
{
$rootScope.enableDirective=false;
if(userHasOneApp){// checking some at least one app then only do action
$rootScope.appicon="img_url"; // data which i am passing
$rootScope.appname="App_name"; // data which i am passing
$rootScope.enableDirective=true;
}
}]);
custom directive:
app.directive('headerTemplate', function () {
return {
template:'<a ui-sref="/event" ng-click="editIt()">'
+'<img src="{{appicon}}"></a>'
+'<span>{{appname}}</span>',
scope:{
appname:'=',
appicon:'='
}
};
});
Header view :
<div> class="headerdiv">
<ul ng-if="enableDirective">
<li header-template appicon="appicon">
</li>
</ul>
</div>
Main view :
<div> class="maindiv">
<ui-view></ui-view> <!--basically I want to append template here -->
<button>Add next template</button>
</div>
Where I am doing wrong ?
Well i was facing the same issue
Check out the following link
This will surely help you.
I have implemented this and it worked in my scenario where i wanted to serve a directive when required i.e Lazy Loading of directive
https://www.codeproject.com/Articles/838402/Lazy-loading-directives-in-AngularJS-the-easy-way
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 === '') {
...
}
I've checked various ressources and all leads to a bug that was closed long ago. Here is a similar issue, but I think mine is slightly different and persists up to at least angular.js 1.5.
I'd like to validate, if this is a new angular bug or if I missed something, since I'm relatively new to angular.
My problem
Check this plunker for a short demo of the issue
I've got a directive <infos> rendered inside a ng-repeat block
<body ng-controller="MainCtrl" class="container">
<div ng-repeat="item in items">
<h2>{{item.title}}</h2>
<infos></infos>
</div>
</body>
The <infos> directive has a ng-repeat block on its first element
<div ng-repeat="(infoId, color) in item.infos">
<p style="background: {{color}}">{{color}}</p>
</div>
The directive is loaded with templateUrl and replace:true.
.directive('infos', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'infos.html'
};
})
The unexpected result:
When rendered, the directives of all iterations of the surrounding ng-repeat are rendered on the last ng-repeat of the outside loop and not as expected on each iteration of the surrounding ng-repeat. Check the plunker demo for a better understanding, what's going wrong.
Workarounds
I found several workarounds to the problem:
Wrapping the ng-repeat element in the directive by any other element
<div>
<div ng-repeat="(infoId, color) in item.infos">
<p style="background: {{color}}">{{color}}</p>
</div>
</div>
Leaving away the replace: true
Using template instead of templateUrl. Inlining the template code.
Using <script type="text/ng-template" id="infos.html">... to load the template
There might be another solution, using the $templateCache but I couldn't figure out how to preload the template, yet.
Say in my controller somewhere I have:
$scope.elements = [document.getElementById('a'), document.getElementById('b')];
and I have valid elements somewhere in the document with IDs of a and b.
I'd like to interpolate these elements directly in an HTML template, without writing JavaScript. I tried the following, and it did not work.
<div ng-repeat="e in elements">
{{ e }}
</div>
Is this possible?
More information about what I'm doing:
I have content (several custom directive elements which load up their own data via AJAX) that I want to disperse between several columns. The column of the content elements will change, and the number of columns will change.
<column-resizer options="columnResizerOptions">
<content1></content1>
<content2></content2>
...
</column-resizer>
The template for columnResizer currently looks like this:
<ng-transclude></ng-transclude>
<div ng-repeat="column in columns">
<div ng-repeat="element in column">
{{ element }}
</div>
</div>
columnResizerOptions is information about how to resize the columns and where to place the content in the columns. In the link function for the columnResizer, I use transclude to grab content1-contentn and place them in arrays corresponding to the column they should be in, which I ngRepeat through in my template (above).
Not sure why you wouldn't treat your whole app the "Angular way", but you could write a directive to do this:
angular.module('demoApp', [])
.directive('interpolateHtml', function() {
return {
restrict: 'A',
scope: {
htmlElement: '='
},
link: function postLink(scope, element) {
element.append(scope.htmlElement);
}
}
})
And use it in your HTML like this:
<div ng-repeat="e in elements">
<div interpolate-html html-element="e"></div>
</div>
Here's a working plnkr: http://plnkr.co/edit/5NvMA1x0C8TcwdLO2FNK?p=preview
It is possible.
$scope.elements = [ (document.getElementById('a') || {}).outerHTML ];
and
<div ng-repeat="e in elements">
<div ng-bind-html="e"></div>
</div>
You won't get data binding this way. You can use innerHTML or jqLite html() instead to get rid of extra wrappers.
get them into the DOM without using append, as it would be cleaner.
It wouldn't. A directive with nested directives or with DOM modifications in link is proper way in this case, you don't have to use data binding everywhere just because Angular promotes it.
Using the AngularJS,
I want to use ng-include to make my code generic.
So here is my problem:
I want to have my 'ang-attribute.html' file used in the ng-include to be as generic as possible, and for that I want to get the attribute name outside of the html.
With something like this:
<div data-field-name="display_name">
<div ng-include="'ang-attribute.html'"></div>
</div>
In my html file I would like to use the data-field-name, and then use it again with a different value.
I tried to get it via DOM... and couldn't find a way to do that.
So in my ng-include html file I have the following line:
<div ng-controller="attributeCtrl">
<div class="my-profile-constant-text">{{displayAttribute}}:</div>
....
</div>
Maybe there is a way to pass a constructor to the controller and then I could pass my data-field-name into it?
Or any other solution?
Thanks!
I believe you want to be looking at directives, this has an attributes object easily accessible after the dom has rendered to re-use.
Html
<my-directive field-name="display_name"></my-directive>
Directive
angular
.module("myApp", [])
.directive("myDirective", function() {
return {
restrict: "E",
template: "<div>{{exposeAttribute}}</div>",
//templateUrl: "mytemplate.html",
link: function(scope, element, attr) {
scope.exposeAttribute = attr.fieldName;
}
};
});
http://jsfiddle.net/mhCaD/