I am quite new to Angularjs . I am working on an app which use Angularjs for front end and django-tastypie for back end. What I am doing is i have a topics list where topics are nested in each others as child parents for multiple levels. these levels can be multiple. my data structure is as
[{'id':1,
'name':'science',
'subtopics':[{
'id':1,
'name':'chemistry',
'subtopics':[{'id':1,
'name':'atom',
'subtopics':[{'id':1,'name':'science',:'subtopics':[]]
]]}}
I may have this nested list for multiple levels . What i want to do is that i want to do is that if I select an element from select list which has subtopics, a HTML is appended to the template and selected elements subtopics become its elements . If there is only one level there will be only one select menu on the template.
Tell me how can I represent this tree in Template using angular. or tell if anyone have any better idea.
my desired HTML rendering is like
<label>Topic Level 1:</label>
<select ng-model="qt_topic_level_1" ng-options="topic.name for topic in qt_topicslist_level_1" ng-change="showSecondLevelTopics()">
</select></br>
<label ng-show="secondleveltopicslabel" ng-true-value="true" ng-false-value="false">Topic Level 2:</label>
<select ng-model="qt_topic_level_2" ng-options="topic.name for topic in qt_topicslist_level_2" ng-change="getThirdleveltopics()" ng-show="secondleveltopicslist" ng-true-value="true" ng-false-value="false" >
</select></br>
means if the subtopics are available in the selected topics list a select tag should be appended to the template to select the sub topic and so on. First time I want to show the root elements only. and then on click want to show the subtopics level by level.
You need to split your template in to two. The first one can by used as recursion container, the "main" topics template:
<div ng-repeat="topic in topics">
<div include-tpl="path/to/topic/tpl-recursive"></div>
</div>
And the single topic's template:
<div class="topic">
<!-- ..topic content.. -->
<div class="subtopics">
<div ng-repeat="topic in topic.subtopics">
<div include-tpl="path/to/topic/tpl-recursive"></div>
</div>
</div>
</div>
I use custom include-tpl directive because ngInclude creates new scope (or used to create, I'm currently using a bit dated version of angular):
.directive('includeTpl', ['$compile', '$http', function($compile, $http){
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
var replace = typeof $attrs.replace !== 'undefined';
$scope.$watch($attrs.includeTpl, function(value) {
if (!value) return;
$http.get(value).then(function(html){
var content = $compile(html)($scope);
if (replace) $element.replaceWith(content);
else $element.html(content);
});
});
}
};
}])
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
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.
AngularJS Verion: 1.3.8
JSFiddle: http://jsfiddle.net/uYFE9/4/
I've been working on a small AngularJS application, and ran into a bit of a problem. I have an ng-repeat on a page, which fills in the contents of a form. The amount of items in the form is defined by a dropdown bound to a model, and populated using ng-options. Something like:
<select id="testAmount" ng-model="selectedItem" ng-options="item.name for item in items"></select>
<form role="form" name="testForm" ng-if="!complete">
<div ng-repeat="i in getNumber(selectedItem.number) track by $index">
{{$index}}
</div>
</form>
Complete is set to false in the beginning, and hitting a Next button will toggle complete and hide the form and dropdown. A Back button will then toggle complete back, and show the form again.
The problem I'm having is with the ng-if on the select (and previously, I had the form wrapped in a div with the same ng-if - same problem). The ng-repeat no longer updates when the select dropdown is changed. Removing the ng-if on the select restores the ng-repeat to working order.
I'm wondering if there's something strange I'm doing with the nesting here, or if it's actually a bug? You can test it out on the JSFiddle linked above. The $index should be printed the number of times on the dropdown, but isn't.
Interestingly enough - when debugging the problem on my local machine, having FireBug open fixed the issue.
This is because of ng-if creating a child scope and how prototypical inheritance works with primitives. In this case, the primitive is selectedItem that you are setting by the <select>, but is actually being set on the child scope and shadows/hides the parent scope property.
In general you should always use a dot (.) with ng-models:
$scope.selection = {selectedItem: undefined};
And in the View:
<div ng-if="!complete">
<select ng-model="selection.selectedItem"
ng-options="item.name for item in items"></select>
</div>
ng-if is causing you some scoping issues (which messes with the binding).
Here is an updated jsfiddle that you could use as a work around. Essentially, this example wraps another div around the items that you want to end up hiding. And then adds a next function so that the same scope is affected during the click that sets complete to true.
HTML:
<div ng-app="test">
<div ng-controller="TestCtrl">
<div ng-if="!complete">
<div>
<label for="testAmount">Amount:</label>
<select id="testAmount" ng-model="selectedItem" ng-options="item.name for item in items"></select>
</div>
<form role="form" name="testForm">
<div ng-repeat="i in getNumber(selectedItem.number) track by $index">
{{$index + 'hi'}}
</div>
<button class="btn btn-default" value="Next" title="Next" ng-click="next()">Next</button>
</form>
</div>
<div ng-if="complete">
</div>
</div>
</div>
JS:
angular.module('test', [])
.controller('TestCtrl', function($scope) {
$scope.complete = false;
$scope.items = [
{ name: '2', number: 2 },
{ name: '3', number: 3 },
{ name: '4', number: 4 }
];
$scope.selectedItem = $scope.items[0];
$scope.getNumber = function (number) {
return new Array(number);
};
$scope.next = function() {
$scope.complete = true;
};
})
I believe the problem is with your select statement inside an ng-if the selectedItem is never getting set. If you just don't want to show that dropdown when !complete change it to an ng-show and it works fine.
<div ng-show="!complete">
As to WHY the ng-model is not being bound inside the ng-if, I don't really know but it does make some sense in that you are trying to do a conditional bind which is a bit screwy
I have the following fiddle setup to demonstrate what I am experiencing.
http://jsfiddle.net/BjornJohnson/ndxnG/25/
My controller uses these objects: page => rows => columns => pieces. A page has rows. A row has columns. A column has pieces.
There is a custom directive ("piece"). The point of the directive is to be able to provide different types of HTML elements based on the "type" of the piece (so-called dynamic templating).
I keep running into problems when I remove pieces from a column. I am modifying the model, but it's not reflected in the view. It works when the ng-repeat is on a wrapper, but not on the custom directive itself, so I think something is clashing with the ng-repeat directive.
I apologize in advance for using all caps, but I CANNOT USE A WRAPPER DIV FOR THE NG-REPEAT :)
The code in question is both in the view and the directive.
Here's part of the custom directive:
var template = templates[scope.piece.key];
var replacement = $($compile(template)(scope));
element.replaceWith(replacement);
Here's part of the view:
<div ng-repeat="column in row.data.columns" ng-class="column.data.clss">
<!-- notice wrapper for ng-repeat - THIS VIOLATES MY REQUIREMENTS -->
<div ng-repeat="piece in column.data.pieces">
<piece></piece>
</div>
</div>
versus...
<div ng-repeat="column in row.data.columns" ng-class="column.data.clss">
<!-- notice ng-repeat on custom directive -->
<piece ng-repeat="piece in column.data.pieces"></piece>
</div>
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>