I know that if I use the directive ng-repeat, like below, I get every element inside and including the div to repeat on the DOM.
<div class="col col-3" ng-repeat="movie in popular" >
<figure>
<img ng-src="{{movie.backdropURL}}" alt="{{movie.code}}">
<div class="overlay"></div>
<figcaption>{{movie.code}}</figcaption>
<!-- <span class="extra-info">{{movie.extra}}</span> -->
<span class="price">{{movie.price}}</span>
</figure>
</div>
However now I want to have some parent elements that won't repeat but will use the same scope object for their children, that will then repeat.
So, I would like to do a repeater that would append the properties of the scope into their parent, something like this:
<ul class="parent1">
<li><img src={{myScope[0].imgUrl}}></li>
<li><img src={{myScope[1].imgUrl}}></li>
<li><img src={{myScope[2].imgUrl}}></li>
</ul>
<div class="parent2">
<span>{{myScope[0].description}}</span>
<span>{{myScope[1].description}}</span>
<span>{{myScope[2].description}}</span>
</div>
I would like to know if it is possible to reuse a native angular directive (I would prefer not to run the same repeater every time for every parent) where it could append the element to the parent. If not, do you have any suggestion for a solution. I've looked up some links for custom directives I haven't succeeded in applying them. So if you have a 'beginners' custom directive tutorial that could help me go on the right direction, it would be highly appreciated.
I don't know if I understand exactly what you are asking for.
Anyway, the ngRepeat directive is placed in the DOM under a particular parent, so you cannot run it once and append the leaves under different parents. The only way to do that is to create a custom directive that runs a loop internally and sets the leaf under the parent of your choice.
That is:
angular
.module('mymodule')
.directive('mydirective', mydirective);
function mydirective(){
var directive = {
restrict: 'A'
, link: link
}
return directive;
function link($scope, elem, attrs) {
for(var i=0;i<$scope.myScope.length;++i){
var el1 = angular.element('<li><img src='+$scope.myScope[i].imgUrl+'></li>'),
el2 = angular.element('<span>'+$scope.myScope[i].description+'</span>');
elem.find('.parent1').append(el1);
elem.find('.parent2').append(el2);
}
}
}
Please let me know if I misunderstood your goal.
Check this:
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<ul class="parent1">
<li ng-repeat="item in myScope">
<img ng-src={{item.imgUrl}}>
</li>
</ul>
<div class="parent2">
<p ng-repeat="item in myScope"><span>{{item.description}}</span></p>
</div>
</div>
Controller:
angular.module('myApp', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.myScope = [
{imgUrl:"someUrl1", description: "this is first url"},
{imgUrl:"someUrl2", description: "this is second url"}
]
}]);
Acceptable :) ?
Related
I have a ng-repeat like this:
<div ng-app="myApp" ng-controller="Ctrl">
{{ctrlTest}}<hr/>
<div ng-repeat="elements in filter">
<div>
<li ng-repeat="(key,value) in filter.producers" ng-show="value">
{{key}}<a ng-click="filter.producers.key=false"> X</a>
</li>
</div>
{{filter.producers}}
</div>
angular.module('myApp', [])
.controller('Ctrl', function($scope) {
$scope.ctrlTest = "Filters";
$scope.filter = {"producers": {"Ford":true,"Honda":true,"Ferrari":true}}
});
I am trying to make a ng-click that would set each label to false when clicking in a link, but I haven't achieved to do it properly as the key values are not fixed (they should be treated as variables).
So far I have tried it his way.
http://jsfiddle.net/Joe82/wjz8270z/5/
Thanks in advance!
Ps: I cannot change the json structure.
You just need to access the element of object by its key, to ensure that there references would not get lost & binding will work
<li ng-repeat="(key,value) in filter.producers" ng-show="value">
{{key}}<a ng-click="filter.producers[key]=false"> X</a>
</li>
Forked Fiddle
You also call a function and set value false
HTML
<li ng-repeat="(key,value) in filter.producers" ng-show="value">{{key}} {{value}}<a ng-click="setValue(key)"> X</a>
JS
$scope.setValue = function(key){
$scope.filter.producers[key.toString()] = false;
}
see this link http://jsfiddle.net/wjz8270z/8/
I guess it is best to describe it with a picture. I have an angular app and here is a simple view.
Obvious explanation: list shows all the entities, if you click on an entity you can edit it in the form that is hidden by default and similar action applies to adding a new entity.
the issue
I know it is basic example so here the solution might be an overkill but I want to separate the logic of 'Add new entity', 'Edit entity' and 'Entities list'. I thought I could implement it like this:
<div ng-include="'userAddForm.html'"
ng-show="???"
ng-controller="AddUser as add">
</div>
<div ng-include="'userEditForm.html'"
ng-show="???"
ng-controller="AddEdit as edit">
</div>
<div class="panel panel-default">
... list managed by the current controller
</div>
What I miss
I have a difficulty in sharing a state of the hidden parts. For example some boolean flag. For instance:
Click on the entity shows the edit form
Save/Cancel in the edit form hides the part
Then, I think the first step is the responsibility of list-controller, but save/cancel part goes to edit-controller. It would be only possible to share the value with a service included in both - but that does not seem reasonable either.
I think there is some simple solution I can not see and I am open for any advice. Thanks!
If your goal is a simple solution with just a boolean being toggled in the model, you can use child controllers like this:
http://plnkr.co/edit/P1ncToJwqvxt9F9MTF5E?p=preview
The child controllers will inherit the scope of the parent controller and can directly edit the values. I have the edit child controller filtering for editMode==true, so when the parent changes that value, the child controller automatically shows the item. All changes are updated live and the child controller simply toggles the editMode property to remove it from the editing area.
Similar logic is used for the add child controller.
The views look like this:
index.html
<div ng-controller="myCtrl">
<div ng-controller="addCtrl" ng-include="'userAddForm.html'">
</div>
<div ng-controller="editCtrl" ng-include="'userEditForm.html'">
</div>
<h1>Listing</h1>
<ul>
<li ng-repeat="item in items | filter:{addMode:false}">
{{item.id}}
{{item.name}}
<button ng-click="startEditing(item)">[ edit ]</button>
</li>
</ul>
<button ng-click="startAdding()">[ add ]</button>
<div>Debug:<br>{{items}}</div>
</div>
userAddForm.html
<ul>
<li ng-repeat="item in items | filter:{addMode:true}">
<input type="text" ng-model="item.id">
<input type="text" ng-model="item.name">
<button ng-click="add(item)">[ add ]</button>
<button ng-click="cancel(item)">[ cancel ]</button>
</li>
</ul>
userEditForm.html
<ul>
<li ng-repeat="item in items | filter:{editMode:true}">
<input type="text" ng-model="item.id">
<input type="text" ng-model="item.name">
<button ng-click="save(item)">[ save ]</button>
</li>
</ul>
And the controllers look like this:
angular.module('myApp.controllers',[])
.controller('addCtrl', function($scope) {
$scope.add = function(item) {
item.addMode = false;
}
$scope.cancel = function(item) {
$scope.items.pop(item);
}
})
.controller('editCtrl', function($scope) {
$scope.save = function(item) {
item.editMode = false;
}
})
.controller('myCtrl', function($scope) {
$scope.items = [
{name:'aap', id:"1", editMode:false, addMode:false},
{name:'noot', id:"2", editMode:false, addMode:false},
{name:'mies', id:"3", editMode:false, addMode:false},
{name:'zus', id:"4", editMode:false, addMode:false}
];
$scope.startAdding = function(){
$scope.items.push({addMode:true});
};
$scope.startEditing = function(item){
item.editMode = true;
};
});
You can achieve this using Angular state routing.In which you will create state (different views) like -
header
addEntity
editEntity
listEntity
refer https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views
Sharing state can be implemented by creating a service which is than injected to all interested párties (controllers), service can hold data which controllers can be bound to and display in template. Services in Angular JS are singletons so all the controllers are accesing and mutating shared state.
I am trying to display a binary tree of elements, which I go through recursively with ng-include.
What is the difference between ng-init="item = item.left" and ng-repeat="item in item.left" ?
In this example it behaves exactly the same, but I use similiar code in a project and there it behaves differently. I suppose it's because of Angular scopes.
Maybe I shouldn't use ng-if, please explain me how to do it better.
The pane.html is:
<div ng-if="!isArray(item.left)">
<div ng-repeat="item in [item.left]" ng-include="'Views/pane.html'">
</div>
</div>
<div ng-if="isArray(item.left)">
{{item.left[0]}}
</div>
<div ng-if="!isArray(item.right)">
<div ng-repeat="item in [item.right]" ng-include="'Views/pane.html'">
</div>
</div>
<div ng-if="isArray(item.right)">
{{item.right[0]}}
</div>
<div ng-if="!isArray(item.left)">
<div ng-init = "item = item.left" ng-include="'Views/pane.html'">
</div>
</div>
<div ng-if="isArray(item.left)">
{{item.left[0]}}
</div>
<div ng-if="!isArray(item.right)">
<div ng-init="item = item.right" ng-include="'Views/pane.html'">
</div>
</div>
<div ng-if="isArray(item.right)">
{{item.right[0]}}
</div>
The controller is:
var app = angular.module('mycontrollers', []);
app.controller('MainCtrl', function ($scope) {
$scope.tree = {
left: {
left: ["leftleft"],
right: {
left: ["leftrightleft"],
right: ["leftrightright"]
}
},
right: {
left: ["rightleft"],
right: ["rightright"]
}
};
$scope.isArray = function (item) {
return Array.isArray(item);
}
});
EDIT:
First I run into the problem that the directive ng-repeat has a greater priority than ng-if. I tried to combine them, which failed. IMO it's strange that ng-repeat dominates ng-if.
It's a little hacky, but I am passing variables to an ng-include with an ng-repeat of an array containing a JSON object :
<div ng-repeat="pass in [{'text':'hello'}]" ng-include="'includepage.html'"></div>
In your include page you can access your variable like this:
<p>{{pass.text}}</p>
Pass parameter to Angular ng-include
You don't need that. all ng-include's sources have the same controller. So each view sees the same data.
What is the difference between ng-init="item = item.left" and ng-repeat="item in item.left"
[1]
ng-init="item = item.left" means - creating new instance named item that equals to item.left. In other words you achieve the same by writing in controller:
$scope.item = $scope.item.left
[2]
ng-repeat="item in item.left" means create list of scopes based on item.left array. You should know that each item in ng-repeat has its private scope
I am trying to display a binary tree of elements, which I go through recursively with ng-include.
I posted in the past answer how to display tree by using ng-include.
It might helpful: how-do-display-a-collapsible-tree
The main part here that you create Node with id wrapped by <scipt> tag and use ng-repeat:
<script type="text/ng-template" id="tree_item_renderer">
<ul class="some" ng-show="data.show">
<li ng-repeat="data in data.nodes" class="parent_li" ng-include="'tree_item_renderer'" tree-node></li>
</ul>
</script>
<ul>
<li ng-repeat="data in displayTree" ng-include="'tree_item_renderer'"></li>
Making a generic directive instead of ng-include is a cleaner solution:
Angular passing scope to ng-include
I am using ng-include with ng-repeat of an array containing string. If you want to send multple data so please see Junus Ergin answer.
See my code Snippet:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="">
<div ng-repeat="name in ['Sanjib Pradhan']" ng-include="'your_template.html'"></div>
<div ng-repeat="name in ['Chinmay Sahu']" ng-include="'your_template.html'"></div>
<script type="text/ng-template" id="your_template.html">
{{name}}
</script>
</div>
I got a ng-repeat with thousands of item in it, so I decided to tryout bindonce to reduce the number of watches. But I couldn't figure out how to use it properly.
So now I got the following code:
<div ng-repeat="card in cards">
<div class="item-box" draggable="{{card.category}}" itemId="{{card._id}}">
<img ng-src="{{card.image}}" width="100%" height="100%">
</div>
</div>
As I read in the bindonce doc, I should add the directive and use the bo-* directives, so I fugured out this:
<div ng-repeat="card in cards" bindonce>
<div class="item-box" draggable="{{card.category}}" itemId="{{card._id}}">
<img bo-src="card.image" width="100%" height="100%">
</div>
</div>
So my question is how I can also use {{card.category}} and {{card._id}} using bind-once?
bo-attr bo-attr-draggable="card.category" bo-attr-itemId="card._id"
seems not to work, I'm not getting any errors, just nothing happens.
Result looks like
<div class="item-box ng-scope" bo-attr="" bo-attr-draggable="card.category" bo-attr-itemid="card._id" draggable="Pants" itemid="m--Pi">
bo-attr doesn't actually seem like what you want to be doing, you just want a directive to evaluate and bind data without creating any watches. I made a plnkr that I think is what you want: http://plnkr.co/edit/sFPAjlRCkDuXU5UiM1U1?p=preview
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
// html
<div directive="name"></div>
// Dummy directive
app.directive('directive', function() {
return {
template: '<div bindonce bo-text="val"></div>',
compile: function() {
return {
pre: function(scope, elt, attrs) {
scope.val = scope.$eval(attrs.directive);
}
};
}
}
})
Woo no watches!
Let me know if I misunderstood something.
I'm building my first Angular app, but am having a bit of trouble getting something to work. I have a video container that will be hidden until $scope.video.show = true; I'm trying to set this value when I click on a link. I'm trying to make that happen in a directive. Any help would be appreciated.
html:
<div ng-controller="AppCtrl">
<div ng-cloak
ng-class="{'show':video.show, 'hide':!video.show}">
// youtube iframe content, for example
</div>
<div>
<ul>
<li>
<h3>Video Headline 1</h3>
<button type="button"
video-show
data-video-id="jR4lLJu_-wE">PLAY NOW 〉</button>
</li>
<li>
<h3>Video Headline 2</h3>
<button type="button"
video-show
data-video-id="sd0f9as8df7">PLAY NOW 〉</button>
</li>
</ul>
</div>
</div>
javascript:
var thisViewModel = angular.module("savings-video", [])
.controller('SavingsVideoController', function($scope) {
$scope.video = {
show : false,
videoId : ""
};
};
thisViewModel.directive("videoShow", function(){
return{
restrict: 'A',
link: function(scope , element){
element.bind("click", function(e){
var $this = angular.element(element);
$this.closest('li').siblings().addClass('hide'); // hide the other one
$this.closest('li').removeClass('hide'); // keep me open
scope.video.show = true; // doesn't work.
// what is the best way to do this?
});
}
}
});
I see a few things you can improve.
Checkout ngShow/ngHide and ngIf; they'll give you toggle-ability more easily than trying to do it from scratch.
Think in angular. Rather than trying to use logic to modify the DOM on your own, simply setup your rules using angular directives, and let the framework do the rest for you.
For example, it seems like this is more what you want.
<div ng-controller="AppCtrl">
<div ng-cloak ng-show='video.show">
// youtube iframe content, for example
</div>
<div>
<ul ng-switch="video.videoId">
<my-video my-video-id="jR4ABCD" my-headline="Video Headline 1" ng-switch-when="myVideoId" my-video-manager="video" />
<my-video my-video-id="al1jd89" my-headline="Video Headline 2" ng-switch-when="myVideoId" my-video-manager="video"/>
</ul>
</div>
</div>
What I changed is making your iframe show conditionally with ngShow, and using ngSwitch to control which video appears (the appearing video is based on the $scope's video.videoId). Then, I turned your <li>s into a directive called my-video, which ends up looking like this
thisViewModel.directive("my-video", function(){
return{
restrict: 'E',
replace: true,
scope: {
myVideoId = "=",
myHeadline = "=",
myVideoManager = "="
},
template = '<li><h3>{{myHeadline}}</h3><button type="button" ng-click="play()">PLAY NOW 〉</button></li>',
link: function(scope , element){
scope.play = function(){
myVideoManager.show = true;
/*whatever you want here, using scope.myVideoId*/
}
}
}
});
This directive does exactly what your old HTML did, but brings it into the angular framework so you can access the properties you're looking for. By using the raw angular directives, I eliminate the need for any manual UI logic; I don't need to access element at all anymore, and both my HTML and JavaScript are cleaner. There's certainly room for improvement here, even, but I would say that this is closer to the right track.
It takes practice to get more familiar with, but following the guidelines in the SO link above will help.
EDIT
Sorry, think I missed a requirement the first time around. If you want both videos to show when none are selected, don't use ng-switch; just set up some manual ng-shows.
<div>
<ul>
<my-video my-video-id="jR4ABCD" my-headline="Video Headline 1" ng-show="myVideoId == video.videoId" my-video-manager="video" />
<my-video my-video-id="al1jd89" my-headline="Video Headline 2" ng-show="myVideoId == video.videoId" my-video-manager="video"/>
</ul>
</div>
Since ng-switch is really just a shortcut for ng-show anyways, it amounts to the same thing; the logic just got moved into the ng-show attribute instead.
Also, if you have an array of videos, checkout out ng-repeat; it will let you repeat your video tag multiple times automatically, instead of by hand.
<ul>
<my-video ng-repeat='aVideo in myVideoArray' my-video-id='aVideo.videoId' my-headline...(and so on)>
</ul>
Well your controller names don't match up. Try changing AppCtrl to SavingsVideoController.
You only need a very simple solution.
HTML
<div ng-controller="AppCtrl">
<div ng-cloak ng-show="view.show">
<!-- Use ng-show is more convenient -->
</div>
<div>
<ul>
<li>
<h3>Video Headline 1</h3>
<button type="button"
ng-click="view.show = true"
data-video-id="jR4lLJu_-wE">PLAY NOW 〉</button>
<!-- You don't need an extra directive to change view.show -->
</li>
<li>
<h3>Video Headline 2</h3>
<button type="button"
ng-click="view.show = true"
data-video-id="sd0f9as8df7">PLAY NOW 〉</button>
</li>
</ul>
</div>
</div>
JS
var thisViewModel = angular.module("savings-video", [])
.controller('SavingsVideoController', function($scope) {
$scope.video = {
show : false,
videoId : ""
};
};
// No need to create another directive