I'm quite new to AngularJS and have read a little bit about how directives work. I'm still not quite sure about how to go about this problem though.
Here's the setup:
I have buttons on a view like so:
<div class="parent">
<button class="firstButton" type="button" ng-click="fetchData(param1, param2)"></button>
<button class="secondButton" type="button" ng-click="fetchData(param1, param2)"></button>
<button class="thirdButton" type="button" ng-click="fetchData(param1, param2)"></button>
<button class="fourthButton" type="button" ng-click="fetchData(param1, param2)"></button>
</div>
<div class="dataPanel">
<!-- This is where DIVs will be placed -->
</div>
Then in my controller I have:
// Init value of counter here
var counter = 0;
$scope.fetchData = function (param1, param2) {
// Do something with parameters here
counter++;
}
Ideal scenario would be when user clicks on firstButton, fetchData() initiates, getting data from a factory, then increments value of counter. A div would be created from counter value change. The number of divs created in the DOM would depend on counter value. Each created div is also populated with data gotten from each respective fetchData(). This goes on as user clicks on more buttons, although in my current project, I've limited the number of allowed dataSet duplicates.
End result of the HTML would then be:
<div class="dataPanel">
<div class="dataSet"> <!-- from firstButton -->
<p>{{dataFromFirstButton}}</p>
</div>
<div class="dataSet"> <!-- from secondButton -->
<p>{{dataFromSecondButton}}</p>
</div>
<div class="dataSet"> <!-- from thirdButton-->
<p>{{dataFromThirdButton}}</p>
</div>
</div>
What's the Angular way of doing this? I've read directives can pretty much do everything when it comes to DOM manipulation but have no idea on where to start when it comes to actually constructing the directive.
One way to accomplish this is to use ng-repeat directive to draw the divs based on an array, that is initially empty. You can set the html up so that a div is drawn for each object in the array, since the array is empty no div is initially drawn.
<div class="dataSet" ng-repeat="n in arrays[0]">
This div from button 1 contains: {{n.data}}
</div>
When you click on the buttons you can add an object, containing any needed data, to the array. Then a div will be drawn for the appropriate ng-repeat directive.
$scope.arrays = [ [], [], [], []];
$scope.fetchData = function (data, idx) {
$scope.arrays[idx].push({data: data});
}
See plunker here.
Related
I have below code to display records using ng-repeat:
<div class="gallery">
<div ng-cloak
ng-repeat="photo in photos| orderBy:'-id'"
ng-mouseLeave = "delete_btn = !delete_btn"
ng-mouseEnter = "delete_btn = !delete_btn"
class="gallery_block"
id="photo_block_[[photo.id]]">
<span title="delete photo" ng-show="delete_btn" class="delete_btn_span" rel="[[photo.id]]" id="delete_photo_[[photo.id]]">
<img src="{{asset('frontend/images/cross_icon.png')}}">
</span>
<p>
<a href="javascript:;">
<img ng-click="showImagePopup([[photo.path_popup_thumbnail]]);" src={{$public_path}}./image.php?width=149&height=109&cropratio=2:1.4&image=[[photo.path_popup_thumbnail]] alt="">
</a>
{{--<span> </span>--}}
</p>
</div>
</div>
I have to delete records for that I first have to call a function using $http and then I remove element from photos array:
var index = $scope.photos.indexOf($('#photo_block_'+id));
$scope.photos.splice(index, 1);
Then I remove element from DOM:
$('#photo_block_'+id).remove();
Update
I have called deletePhoto function using jqyery like below:
$(".gallery").on('click','span.delete_btn_span',function()
{
$scope.deletePhoto($(elem), $(elem).attr('rel'));
});
deletePhoto function further do all the work of deleting elements etc.
But it does not remove element. Where as all the selectors are fine.
Is removing element from photos array causing problem?
With angular JS, you have to deal with data. So you should put an ng-click="delete(photo)" on your span.
and create a method in your controller :
$scope.delete = function(photo) {
// delete froms scope.photos here...
}
to delete the photo from the list.
Your code does not work because $scope.photos.indexOf($('#photo_block_'+id)) surely return -1, because you're looking for $('#photo_block_'+id), which is a jquery object in a list of simple json object (i suppose).
So the splice method does anything, and the $scope.photos list remain unchanged, so the view is not changed, because it's build from the list by the ng-repeat directive.
ps: you should never manipulate dom element from controller, if you need to do that, you should create directive.
My html:
<div id="contentDiv">
<div id="headerDiv" ><div id="titleDiv"> Queries</div></div>
<div id="valuesDiv" ><div id="yearDiv"> 2015</div></div>
<div id="graphDiv" ><div id="chartDiv">graph</div></div>
</div>
Like this div, I have another div but the content in the div is different.
How to add a new div horizontally when I click on hyperlink using angularjs?
How can I do this? please help me out regarding this
Looks like what you need is a two way binding with the ng-model directive. So the idea would be that you bind the new div to a variable in your scope which is initially in an empty or undefined state (for example, there are better ways). When the hyperlink is clicked it calls the function specified by an ng-click directive which will fill your bound object, which in turn will cause the new div to be rendered.
EDIT:
Based on your comments here is a simple example.
HTML page:
<div id="newDiv" ng-repeat="item in items">
<!-- Div content -->
<!-- example -->
<input type="text" ng-model="item.name">
</div>
<input type="button" ng-click="addItem()">
Controller:
$scope.items=[];
$scope.addItem = function() {
var newItem = {};
newItem.name = "new item name";
$scope.items.push(newItem);
}
What's happening here is the data for each div is stored in an array of objects. The ng-repeat directive will repeat the div for each object in the array. You can then fill the elements in the div using the object. Adding a new div is as simple as adding a new item to the array and angular will take care of the rest for you. Please note that I have not tested this example, but hopefully it's enough to point you in the right direction.
RE aligning the divs horizontally, this will be done with CSS, using the inline-block display mode. So you could give the div a class of, for example, "horizontalDiv" and add the following class to your CSS file:
.horizontalDiv {
display: inline-block;
}
On the process of trying to learn angularjs, I am creating a set of divs using ng-repeat. I have five divs in all. I am only displaying the first div using $firstdirective component.
<div class="container" ng-app="demoApp" ng-controller="demoController">
<div class="row" ng-repeat="job in jobs" ng-hide="!$first">
<div class="col-md-4">
{{job}}
</div>
<div class="col-md-4">
<button class="btn-primary btn-xs add" ng-click="showNext($event)" ng-hide="$last">Add</button>
<button class="btn-primary btn-xs delete" style="display: none;">Delete</button>
</div>
</div>
</div>
That works.
Now, each div has two buttons, Add and Delete. When I click add I want to hide the add button and delete button of the current row and open the next row. So I wrote,
$scope.showNext = function(evt) {
var elm = evt.target;
$(elm).hide();
$(elm).next().hide();
$(elm).closest("div.row").next().slideDown();
};
This is not working as ng-hide is overriding my slideDown. What is the angularjs way of doing it?
Also I would like the delete button only on the $last visible row && !first. How to find last visible row in ng-repeat?
Fiddle: https://jsfiddle.net/codeandcloud/u4penpxw/
You shouldn't manipulate the DOM with things like $(elm).hide();, you should use ng-class= to let Angular add classes that hide or show the elements. If you switch your ng-repeat to ng-repeat="job in jobs track by $index", you could write something like that in your showNext()-function:
$scope.showNext = function(index) {
$scope.currentJob = $scope.jobs[index]
}
and call it like ng-click="showNext($index) - of course you'd need to do some refactoring as well. But then you could write something like ng-class="{hidden: (job == currentJob}" to hide all buttons except in the current row.
There's a lot of good thoughts in this q&a: "Thinking in AngularJS" if I have a jQuery background?
I'm trying to do a very simple thing. I'm displaying a list of values with Edit links beside them. Clicking the edit link reveals a form that lets you update the value.
(I've simplified the question so the items just have one field "name". My actual use case has more fields, but is canonically equivalent.)
I've run into something that looks like a bug in Angular, but given my limited experience with it, I'm not so sure. Here's how I'm trying to do it:
<div ng-repeat-start="item in items" ng-controller="ItemCtrl as ctrl" ng-hide="ctrl.isEditing">
<span>Name: {{item.name}}.</span>
<a href='#' ng-click='ctrl.startEditing()'>Edit</a>
</div>
<div ng-repeat-end ng-show="ctrl.isEditing">
<input type='text' ng-model='item.name'/>
<a href='#' ng-click='ctrl.save()'>Save</a>
</div>
My controller looks like this:
app.controller('ItemController', function() {
this.isEditing = false;
this.startEditing = function() { this.isEditing = true; }
this.save = function() { this.isEditing = false; }
});
Clicking on Edit link calls the right controller function, and the first div hides. But the second div does not hide.
When I rearrange the code to look like this (essentially wrapping the two divs with a wrapper element), all is well.
<div ng-repeat="item in items" ng-controller="ItemCtrl as ctrl">
<div ng-hide="ctrl.isEditing">
<span>Name: {{item.name}}.</span>
<a href='#' ng-click='ctrl.startEditing()'>Edit</a>
</div>
<div ng-show="ctrl.isEditing">
<input type='text' ng-model='item.name'/>
<a href='#' ng-click='ctrl.save()'>Save</a>
</div>
</div>
Any idea what is technically wrong with the first version? Note that the <input> boxes do get populated with the right values from item.name.
PS: There's a reason why I'm trying to keep the two divs siblings: in my use case, they are actually implemented as two trs which are supposed to appear right below each other in a table.
It's not a bug from angular but it is quite logical.
<div ng-repeat-start="item in items" ng-controller="ItemCtrl as ctrl" ng-hide="ctrl.isEditing">
<span>Name: {{item.name}}.</span>
<a href='#' ng-click='ctrl.startEditing()'>Edit</a>
</div>
<div ng-repeat-end ng-show="ctrl.isEditing">
<input type='text' ng-model='item.name'/>
<a href='#' ng-click='ctrl.save()'>Save</a>
</div>
If you see the above code you have injected controller only to the first div so obviously sibling div doesn't know what is ctrl or ItemCtrl until and unless you do as in you second way.
So if you want to achieve it as sibling, if you are using routing then add the controller attribute in your route path.
So that the controller will be active for that entire template and you can achieve what you want.
Hope it helps.
Thats because controller has its own scope. When you placed controller ONLY on your first div controllers' scope is limited to only this one div. When you wrapped both your divs inside another and place controller on wrapper, controllers' scope now is all that inside this wrapper. So in this case this works fine and this is not the angular bug
I'm very very new to Angular JS and have the following requirement:
I have an ng-repeat as shown below:
<div class="panel" ng-repeat="(appname, value) in chart.accordionData" style="margin-top: 0;">
<div class="accordion collapsed" data-parent="#accordion1"
data-toggle="collapse"
data-target="#{{appname.replace(' ','')}}">
<div class="accordion-head" initial-select index="{{$index}}">
<div class="arrow chevron"></div>
<h4><i></i>{{appname}}</h4>
</div>
</div>
<div class="accordion-body collapse" id="{{appname.replace(' ','')}}">
<p class="highlightable" >
Some data
</p>
</div>
</div>
The data in this ng-repeat comes from the server.
So the problem statement I have is to perform a click trigger on the first element under ng-repeat, .accordion-head so that the first item in the list is always open. I tried various approaches of putting $watch etc. but when the number of items in the list are same, then the trigger doesn't fire.
(the first item in list has to be clicked even when new data is loaded)
I thought of writing a directive initial-select and perform click based on index but that happens only once. I really need an experts advice.
Any solutions?
I think the simplest option is to use data-ng-init="collapse = !$first;".
Another option:
Have you used Angular UI Bootstrap? Also, are you using $http or $resource to get data from the server?
If you use $resource, you can set a variable on the success callback (interceptor response) to open=true. Then, you can either bind this variable to Angular UI Bootstrap is-open attribute or to your data-toggle. Also, with $resource you can set data-ng-if="chart.accordionData.$resolved" (or chart.$resolved depending on your setup), which is nice.
You can use a watch to watch the array contents.
Check out the following fiddle:
http://jsfiddle.net/akonchady/w0qdsen7/3/
I have used a variable to keep track when the array content changes since directly watching the array will be more memory intensive.
Here is the main code that watches the variable:
$scope.$watch('arrModified', function(newValue, oldValue) {
if(newValue) { //array is modified
//Trigger ng-repeat div click
setTimeout(function() {
$('.item:first').trigger('click');
$scope.arrModified = false;
$scope.$digest();
}, 0);
}
});