I am trying to build ng-template dynamically, In the Data structure, I have a Object that contains list of object of same type or other type of object.
[
{
type: "device",
id: 1,
allowedTypes: ["device","action"],
max: 5,
columns: [
{
type: "action",
id: "1"
},
{
type: "device",
id: 2,
allowedTypes: ["device","action"],
max: 5,
columns: [
{
type: "action",
id: "1"
},
{
type: "action",
id: "2"
}
]
}
]
}
]
My ng-template code:
<script type="text/ng-template" id="and.html">
<div class="container-element box box-red">
<h3>And {{item.id}}</h3>
<ul dnd-list="item.columns"
dnd-allowed-types="item.allowedTypes"
dnd-disable-if="item.columns.length >= item.max">
<li ng-repeat="element in item.columns"
dnd-draggable="element"
dnd-effect-allowed="move"
dnd-moved="item.columns.splice($index, 1)"
dnd-selected="models.selected = element"
ng-class="{selected: models.selected === element}"
ng-include="element.type + '.html'" onload="onloadAction()">
</li>
</ul>
<div class="clearfix"></div>
</div>
</script>
In the ng-template code the first level or different types of object can easily be added, however same type of object does not work properly when added in the 2nd level, it seems the its going in a recursive loop.
This is because the nested ng-template (child) ng-template use of root item.columns from the ng-repeat="element in item.columns".
<li ng-repeat="element in item.columns"
dnd-draggable="element"
ng-include="element.type + '.html'">
</li>
any advise how to resolve this issue.
I have managed to resolve this issue, ng-repeat create a new child scope, however the item from the parent scope is also visible to in the child scope, as this is recursive that mean child template and parent template both have the variable with the same name called item. However the child scope variable item is ignored:
<li ng-repeat="element in item.columns"
dnd-draggable="element"
ng-include="element.type + '.html'">
</li>
in the above snippet the element also has a variable called item, so in the child scope when we call item.columns the item variable of parent is invoked again, to ensure that the item variable of child is not ignored, we need to use ng-init with ng-include and set the current element as item in the child scope as shown below:
<li ng-repeat="element in item.columns"
dnd-draggable="element"
ng-include="element.type + '.html'" ng-init="item=element">
</li>
To read more on this please visit
http://benfoster.io/blog/angularjs-recursive-templates
Related
I have an extremely hierarchical JSON structure as a scope variable in my AngularJS controller. I want to loop around different sections of that variable. I thought about using ng-init to specify where in the structure I am. Here is some code:
my_app.js:
(function() {
var app = angular.module("my_app");
app.controller("MyController", [ "$scope", function($scope) {
$scope.things = {
name: "a",
children: [
{
name: "a.a",
children: [
{ name: "a.a.a" },
{ name: "a.a.b" }
]
},
{
name: "a.b",
children: [
{ name: "a.b.a" },
{ name: "a.b.b" }
]
}
]
}
}]);
});
my_template.html:
<div ng-app="my_app" ng-controller="MyController">
<ul>
<li ng-init="current_thing=things.children[0]" ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
I would expect this to display a list:
a.a.a
a.a.b
But it displays nothing.
Of course, if I specify the loop explicitly (ng-repeat="thing in things.children[0].children") it works just fine. But that little snippet of template code will have to be run at various points in my application at various levels of "things."
(Just to make life complicated, I can get the current thing level using standard JavaScript or else via Django cleverness.)
Any ideas?
ng-init runs at a lower priority (450) than ng-repeat (1000). As a result, when placed on the same element, ng-repeat is compiled first meaning that the scope property created by ng-init won't be defined until after ng-repeat is executed.
As a result, if you want to use it in this manner, you'd need to place it on the parent element instead.
<div ng-app="my_app" ng-controller="MyController">
<ul ng-init="current_thing=things.children[0]">
<li ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
<div>
<ul id="teachers">
<li ng-repeat></li>
</ul>
<ul id="students">
<li ng-repeat></li>
</ul>
</div>
I have two ul elements and dynamic data. For example:
[
{
name: 'Jack'
status: 'teacher'
},
{
name: 'Angelina'
status: 'teacher'
},
{
name: 'Maya'
status: 'student'
},
{
name: 'Kate'
status: 'teacher'
},
{
name: 'Margaret'
status: 'student'
}
]
I want to write some custom directive for ng-repeat, which will generates lists, for students and for teachers, for different ul's.
How can I write directive, with some condition, which will repeat li's in the right ul?
Yes, I can, filter My data and generate two Arrays, for students and teachers and than repeat those Independently.
But, I don't like this way. How it is possible to write one custom directive which will determines, where to repeat current Object?
UPDATE
Okey, I'm new in angular, so I've thought, that there will be something simple trick, something like this:
if(status === 'something')
use this template
else
use this template
So, with your answers I could write conditions which I wanted. Sorry about this, this was stupid decision.
So my actual case is:
I have Breadcrumbs data and main container, which width is equal to 500px.
I want to repeat li in this container and I want to my li's were always always inline.
If my data will be big, or some title will be big and my ul width will be more, than my container, some li elements will be dropped bellow.
because of this, I have two ul elements and lis which won't have there space I want to insert in second ul, which will be hidden and after click on something I will show this ul
Options:
Use in built angular filters. For example:
<ul id="teachers">
<li ng-repeat="person in people | filter: { status: 'teacher' }"></li>
</ul>
plnkr
Split the array in your controller. Both split arrays should still point to the original object (in the original array), so manipulation should be ok.
You can definitely create your own directive, but you will end up encapsulating one of the options above.
Better than write a directive, filter your array javascript with the built-in functions for array.
Example:
HTML
<div ng-controller="ClassroomController as classroom">
<ul id="teachers">
<li ng-repeat="teacher in classroom.teachers track by $index"></li>
</ul>
<ul id="students">
<li ng-repeat="student in classroom.students track by $index"></li>
</ul>
</div>
JAVASCRIPT
function Controller() {
var vm = this;
vm.data = [
{
name: 'Jack'
status: 'teacher'
},
{
name: 'Angelina'
status: 'teacher'
},
{
name: 'Maya'
status: 'student'
},
{
name: 'Kate'
status: 'teacher'
},
{
name: 'Margaret'
status: 'student'
}
];
vm.teachers = vm.data.filter(function(item){return item.status === 'teacher'});
vm.students = vm.data.filter(function(item){return item.status === 'student'});
}
I also think that filtering is the best as already answered. But according to your update you can do something like this in yuor directive controller:
$scope.getTemplateUrl = function() {
if (status == something) {
return '/partials/template1.html';
} else {
return '/partials/template2.html';
}
}
Then define your directive template as follows:
template: '<ng-include src="getTemplateUrl()"/>',
Of course status has to be defined before the directive is rendered.
directive('info', function()
{
return {
restrict : 'E',
template : '<ul> <li ng-repeat="l in list"><div ng-if="check(l)">{{l.name}}</div></li></ul></br><ul><li ng-repeat="l in list"><div ng-if="!check(l)">{{l.name}}</div></li></ul>',
controller : function($scope)
{
$scope.check = function(l)
{
if(l.status=="student")
return true;
else if(l.status=="teacher")
return false;
}
}
};
});
I'm making a fairly complex menu system where it should change depending on the authority that has logged in. Now my question is, have I nested it too much? How do I access for example a title in the items, in the admin within the menus? I'm gonna generate the menus using angularJS's ng-repeat so I need to be able to access it via "item in menus.admin.items.title" for example. I figured I should ask now before I add more in case this isn't a viable option.
This is my menu structure inside the angular controller:
$scope.menus = [{
admin: [{
title: 'Administration',
items: [{
title: 'Hantera utbildningar',
URL: 'mngprograms'
},
{
title: 'Hantera kurser',
URL: 'mngcourses'
},
{
title: 'Hantera lärare',
URL: 'mngteachers'
},
{
title: 'Hantera studenter',
URL: 'mngstudents'
}],
URL: 'administration',
id: 'administration'
}]
}]
Here's my failed attempt at accessing it:
EDIT:
To get a clearer view of the whole thing go here: http://jsfiddle.net/52evmfg9/
<ul id="main-menu">
<li data-ng-repeat="menu in menus" id="{{menu.id}}">{{menu.title}}
<ul data-ng-if="menu.admin">
<li data-ng-repeat="subitem in menu[3].admin.items">{{subitem.title}}</li>
</ul>
</li>
</ul>
How do I write this?
I recommend making your hierarchy structure more explicit. For each menu, you can have a key "submenus" where you can find menus further down in the hierarchy. A useful directive for hiding elements is ng-show.
I would only build one complete menu tree and then set permissions for your users or roles. Otherwise the tree will become unmanageable and there will be repeated data.
Here is an example jsBin:
http://jsbin.com/sixiw/2/edit?html,js,output
To answer the question in the comments:
<div data-ng-repeat="(k,v) in menus">
<div data-ng-repeat="(k,w) in v">
<div data-ng-repeat="(k,x) in w">
<ul data-ng-repeat="(k,y) in x" >
{{k}} : {{y}}
<li ng-if="z.title" data-ng-repeat="(k,z) in y track by $index">
Title: {{z.title}} URL: {{z.URL}}
</li>
</ul>
</div>
</div>
</div>
http://plnkr.co/edit/WLnXBQbbOqveJuHiUeEI?p=preview
i am seeing the example here https://docs.angularjs.org/api/ng/directive/select and this have 3 select with the same value, how can i have different values selected in my options?
i have this:
<li ng-repeat="tarea in tareas">
<input type="checkbox" ng-model="tarea.COMPLETADA" ng-change="updTarea(tarea.ID)"/>
<span class="done-{{tarea.COMPLETADA}}" >{{tarea.NAME}} {{tarea.CLASIFICADORES}}</span>
<select ng-model="selectedOpt"
ng-options="clasificador.NAME for clasificador in clasificadores">
</select>
<button class="buttons delete right" ng-click="delTarea(tarea.ID)"> Eliminar</button>
</li>
so i can have 5,10,15 options, and i want to make a selected item with the value that i have in tarea.CLASIFICADORES, i tried with this
$scope.selectedOpt = $scope.clasificadores[1]
but that make all the options with the same value, like in the example...
how can i make different selected item in my options dynamically with a value i have in my ng-repeat in every item?
i load the data with ajax...
my problem is to set the default selected item with the tarea.CLASIFICADORES. for example, i have a todo list that have a classifier, i want my ng-options to select by default my database value clasifier when the page is load
The problem is, that you are using the same scope variable for all selections. You could store the selected options in an array too like this:
function TestCtrl($scope) {
$scope.items = [
{ id: 1, class: 1 },
{ id: 2, class: 2 },
{ id: 3, class: 1 },
];
$scope.classes = [
{ name: "class 1", id: 1},
{ name: "class 2", id: 2},
{ name: "class 3", id: 3}
];
};
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app>
<div ng-controller="TestCtrl">
<div ng-repeat="currentItem in items">
<select ng-model="selectedClass[$index]" ng-init="selectedClass[$index]=(classes|filter:{id: currentItem.class})[0]" ng-options="class.name for class in classes track by class.id" >
</select>
selected class: {{selectedClass[$index]}}
</div>
</div>
</div>
In this example I take use of the variable $index, which is set by the ng-repeat directive. As the name suggests it contains the current index of the repeat-loop.
UPDATE
I updated the code-snippet so it sets the default value for each select input.
The different items now contain a field with the id of the corresponding class. I initialize the select input with ng-init. With this directive I set selectedClass[$index] which is the selected value for the current item. As we only have the class-id as a property of the items I use a filter to find the corresponding class object with the id (classes|filter:{id: currentItem.class})[0]
To get rid of the filter you could just set the class of each item to the full class-object instead of the id.
I am trying to create JSON to use for a nested list.
I assume I am using incorrect syntax on the second ng-repeat:
<ul>
<li ng-repeat="n in navData">Label = {{n.label}}</li>
<ul>
<li ng-repeat="n.children in n">Child label = {{p.label}}</li>
</ul>
</ul>
When my data model is
var nav = [{
label: 'Pages',
value: 'pages',
children: [{
label: 'Home',
value: 'home'
}, {
label: 'Left Nav',
value: 'left-nav'
}]
}, {
label: 'Components',
value: 'components'
}];
All I am getting in the html is the top level:
Label = Pages
Label = Components
Outer li tag should close after inner li tag with ng-repeat. And you should use different var name and slightly different expression (actually you have name your var as p {{p.label}} somewhere but not in nested loop):
<ul>
<li ng-repeat="n in navData">Label = {{n.label}}<!-- closing li removed //-->
<ul>
<li ng-repeat="p in n.children">Child label = {{p.label}}</li>
</ul>
</li> <!-- closing li tag added //-->
</ul>