How to use Angularjs to dynamically set spinner after table row bound - angularjs

I'm updating the table via an ajax call, and want to display a spin.js spinner while the row.status == pending.
Basically I have a row fragment that successfully toggles from
<div class="spinner">
to
<div class="hide">
as the row computation progresses, (via ajax within a controller).
I'm happy with any mechanism that works!
What I'm struggling to do is, when the value is
<div class="spinner">
have a spinner showing, otherwise have it hidden.
<table class="table table-hover">
<thead>
<tr>
<th>url</th>
<th>status</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="row in result.results">
<td>{{row.link.url}}</td>
<td> <div loadingWidget class="{{(row.status == 'pending' ? 'spinner' : 'hide' ) }}"></div>{{row.status}}</td>
</tr>
</tbody>
</table>
My latest script - basically I don't really know what I'm doing,
app.directive('loadingWidget', function ($rootScope) {
return {
restrict: 'E',
scope: {
field: '=',
attributes: '=',
editMode: '='
},
link: function (scope, element, attrs) {
scope.spinit = function() {
$rootScope.$broadcast('spinit');
}
}
};
});
$scope.$on('spinit', function(element){
//react to event
new Spinner().spin(element);
});

You should use the ngHide directive. I'm having difficulty determining what you want your show/hide condition to be though. The rule is that if the contents of ng-hide evaluate to true, the element will be set to hidden.
If you absolutely need to switch the active class, not just hide the element, user ng-class. That will set the class based on a variable in scope.
<table class="table table-hover">
<thead>
<tr>
<th>url</th>
<th>status</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="row in result.results">
<td>{{row.link.url}}</td>
<td>
<div loadingWidget class="spinner" ng-hide="row.status"></div>
{{row.status}}
</td>
</tr>
</tbody>
</table>
Also, using $broadcast on root scope can be incredibly expensive, and is almost never necessary. In your case I don't see why you can't just call the Spinner().spin method directly.
ngHide docs: https://docs.angularjs.org/api/ng/directive/ngHide
ngClass docs: https://docs.angularjs.org/api/ng/directive/ngClass
Edit:
Okay, so I went through your code more thoroughly. You have several issues here.
First, you directives are kebab case in html, camelcase in javascript. That means your directive should look like: <div loading-widget class="spinner" ng-hide="row.status">.
Also, you have restrict E on your directive attributes. This means it can only be used as an element, and you are using it as an attribute. To fix this, either remove the restrict option or change it to restrict: 'A'.
You don't need the scope on your loadingWidget directive at all, and honestly any time you include Root Scope you are probably doing something wrong.
All of this results in a directive that looks something like this:
app.directive('loadingWidget', function() {
return {
restrict: 'A',
scope: {
field: '=',
attributes: '=',
editMode: '='
},
link: function (scope, element, attrs) {
console.log("elm: ", element[0]);
new Spinner().spin(element[0]);
}
};
});
Plunkr Link
Also, there are already wrapper modules for Spin.js in Angular, so if you don't mind more dependencies that is a good option (Link).
In terms of the logic for hiding the spinner, I would simply use an ngShow with the value of whatever will be in that cell. When you go to update the value, set it to '' or false or undefined. On page load or when it gets changed to one of the values I just mentioned, the spinner will be hidden. Once the value is set, it will no longer be falsy and the element will show. This works as long as you never expect to store false in the variable. In Angular, don't use events unless you really need to, just manipulate scope variables and wait for the digest cycle. Having some simple state variables and then a combination of ngShow and ngHide can make simple state changes in a view easy.
A core value of getting comfortable with writing good Angular code is knowing that you basically never interact with your application outside of scope or directives. Avoid element selectors, and avoid events unless you are sure you need them. Much of Angular was designed to avoid the JQuery like tools, because they can make your code really hard to read.

Related

How can i conditionally display an element using AngularJS

I want to display an element conditionally based on the value of another parameter PaymentTypeid. After setting the condition as below the element Payment Channel is not rendering in the UI:
<tr ng-init="paymentMode='BANK CABS'" ng-if="json.name == 'paymentTypeId' && json.property == '1'">
<td><strong>{{ 'label.heading.paymentchannel' | translate }}:</strong></td>
<td ><span >{{paymentMode}} </span></td>
</tr>
However when i refactor the markup as below the element is showing as :
<tr ng-init="paymentMode='BANK CABS'">
<td><strong>{{ 'label.heading.paymentchannel' | translate }}:</strong></td>
<td ><span >{{paymentMode}} </span></td>
</tr>
PaymentTypeId is in a json array defined as follows in the controller:
scope.details = {};
resourceFactory.auditResource.get({templateResource: routeParams.id}, function (data) {
scope.details = data;
scope.details.paymentMode="";
scope.commandAsJson = data.commandAsJson;
var obj = JSON.parse(scope.commandAsJson);
scope.jsondata = [];
_.each(obj, function (value, key) {
scope.jsondata.push({name: key, property: value});
});
});
In the view PaymentTypeid renders as :
<table class="table" data-ng-show="details.commandAsJson" data-anchor>
<tbody>
<tr data-ng-repeat="json in jsondata">
<td class="width20"><strong> {{json.name}}</strong></td>
<td class="width80">{{json.property}}</td>
</tr>
</tbody>
</table>
Any insight on what i might be getting wrong. Im not entirely sure between using ng-if/ng-show or whether im setting json.property correctly.
Assuming that you have knowledge of scope in AngularJS.
There is a difference between using ng-if and ng-show. Whenever you use ng-if , it creates it own child scope. and you can manage it in custom directive that deals with its child scope (child scope is not available in controller unless you write your code in a way, that will make it available in controller) and you can hack the scope to use it in controller too. But that is not the case in ng-show.
When you use ng-show it will not remove your HTML from the DOM tree but if you use ng-if it will also remove your html from DOM tree. (To assist your confusion which one to use)
You have a scope issue here , if i'm getting it right. Use ng-show and it will work.
<div ng-show="condition">
your html markup
</div>

How to display html after all angularjs processing

I'm working with angularjs and I have a table with rows and columns dynamically created with ng-repeat. Each cell has a directive that has ng-include. While the contents of the cell are processed, ng-repeat is terminated and the table is displayed with empty cells for a moment. I would like to block the page view until all angular processing has been finalized. It is possible?
I load the rows and columns to load a table, ex:
<table>
<tr ng-repeat="row in rows">
<td ng-repeat="col in cols">
<my-directive row="row" col="col" tamplate-cell-url="{{templateCellUrl}}">
</my-directive>
</td>
</tr>
</table>
my directive:
angular.module('app').directive('myDirective', function () {
return {
restrict: 'E',
scope: {
row: '=',
col: '=',
templateCellUrl: '#'
},
replace: true,
link: function (scope, element, attrs, ctrl) {
scope.url = function () {
return scope.templateCellUrl || 'cell-default.html'
}
},
template: '<div ng-include="url()"></div>'
}
})
Yes there is one simple solution for this. Use ng-show or ng-hide for this
<div ng-hide="variable == undefined">
//Your HTML Code
</div>
In this above code example it will hide your html content if the $scope.variable is undefined(means it is not yet initialized). If something gets assigned to it, it will show you the DOM content.
For multiple variables use and condition.
Sounds like a job for ng-cloak directive.
The ngCloak directive is used to prevent the AngularJS html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.
See it here https://docs.angularjs.org/api/ng/directive/ngCloak
So in your cell directive you could do:
<div ng-cloak>
<ng-include src="something.html'"></ng-include>
</div>

Two table cells instead of one angularjs

I'll try to explain my problem as simple as I can. I have something like this:
<table>
<tr>
<th ng-repeat="ordinate in ordinatesX">{{ordinate.ordinateLabel}}</th>
</tr>
</table>
This is just a small peace of my table but it is the problematic one. My ordinate object has an attribute abstract. When the abstract is set to true and DOM to make two th like this:
<th>{{ordinate.ordinateLabel}}</th><th></th>
So the first th is writing ordinate label and the second th needs to be empty.
But when the abstract is set to false I need only one th like this:
<th>{{ordinate.ordinateLabel}}</th>
I've tried something with the simplest custom directive like this:
.directive('ngTest', function() {
return {
restrict: 'A',
replace: true,
template: '<th></th><th></th>'
}
});
But even this wasn't working because of the problem with the replace: true and root element being tr.
Any ideas?
You can combine ng-repeat-start and ng-if
ng-if will not insert element in DOM.
<table>
<tr>
<th ng-repeat-start="ordinate in ordinatesX">{{ordinate.ordinateLabel}}</th><th ng-if="ordinate.abstract" ng-repeat-end></th>
</tr>
</table>

Angular UI-Router ui-sref ignore some elements

I have an interesting problem with the uiSref directive and I haven't been able to find a solution (well an elegant one anyway) anywhere on the web. Basically I have a requirement from a client to be able to click a row in a table of resources and go to the editing view for that resource. Normally the uiSref directive works beautifully, but the problem resides in the fact that I have a Bootstrap dropdown in the last <td> of the table with a bunch of quick actions in it. The HTML looks something like this:
<table class="table table-bordedered table-hover">
<thead>
<tr>
<td>Name</td>
<td>Actions</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="resource in resources" ui-sref="edit({id: resource.id})">
<td ng-bind="resource.name"></td>
<td class="actions-column">
<div class="btn btn-xs btn-default" data-toggle="dropdown">
<i class="fa fa-cog"></i>
</div>
<ul class="dropdown-menu pull-right">
<li>
SOMETHING CRAZY
</li>
</ul>
</td>
</tr>
</tbody>
</table>
The problem is that when I click on the button in the actions column, the uiSref overrides the default action of the dropdown and takes me to the edit page. Now you might be asking yourself "well that's easy, why can't you just stop the propagation of the event!?"... doesn't work. When I add this to the actions column:
<td class="actions-column" ng-click="$event.stopPropagation()">
It kills the functionality of the dropdown menu and nothing shows up. Right now I have a workaround in place where I define an ngClick on the <tr> element that then deciphers where the state should go depending on the element clicked like so:
<tr ng-repeat="resource in resources" ng-click="goToEdit(resource, $event)">
And The JS looks like this:
scope.goToEdit = function(resource, event) {
// if the event.target has parent '.actions-column' or is that column, do nothing else
// invoke $state.go('edit', {id: resource.id})
}
I hate it though and I have a lot of list views like this. All I'm looking for is an elegant and portable solution that hopefully works natively through UI Router like $event.stopPropagation() (Although I've poked through the UI Router source and can't seem to find a workable alternative). Basically I want to have my cake and eat it too. Anyway, it'll be interesting to see what the SO community can come up with or if what I'm asking for is not currently possible. Thanks!
I got it! While looking through the UI Router source some more, it appears that the click event will be ignored if the target attribute is populated on the element that the uiSref resides on. It may not be the most beautiful thing in the world, but it sure is easier than what I was doing before.
NOTE: This only works if you're using the whole jQuery library, not jQLite
So I wrote this directive:
app.directive('uiSrefIgnore', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.on('click', function(e) {
// Find the ui sref parent
var uiSref = elem.parents('[ui-sref]').first();
// Set the target attribute so that the click event is ignored
uiSref.attr({
target: 'true'
});
// Function to remove the target attribute pushed to the bottom
// of the event loop. This allows for a digest cycle to be run
// and the uiSref element will be evaluated while the attribute
// is populated
setTimeout(function() {
uiSref.attr({
target: null
});
}, 0);
});
}
};
});
That way, whenever I want to ignore the javascript event for just the uiSref directive, I can just add this to the html:
<tr ui-sref="edit">
<!-- any other elements -->
<td ui-sref-ignore>The element I care about</td>
</tr>
BOOM! Let me know what you guys think about the implications of this.

Get div items with angular

As in Jquery I use each function ($.each...)to get different elements from a div selection (in my case).
How can i get different row elements content from a table using angularjs?
Example :
<table id="myTable">
<tr><td>one text</td></tr>
<tr><td>two text</td></tr>
<tr><td>three text</td></tr>
</table>
Thanks.
The correct way to do things like this is to make a directive, this is pretty easy to do.
I'm sure you are used to jquery where you would do something like:
<table id="myTable">
<tr><td class="formatted">one text</td></tr>
<tr><td class="formatted">two text</td></tr>
<tr><td class="formatted">three text</td></tr>
</table>
And then do:
$('.formatted').each(function(){ ... format text here ...})
The directive is a very similar concept in angular. This would be the equivalent of the above:
<table id="myTable">
<tr><td formatted-text>one text</td></tr>
<tr><td formatted-text>two text</td></tr>
<tr><td formatted-text>class="formatted">three text</td></tr>
</table>
And then you define a directive:
myApp.directive("formattedText", function(){
return {
link: function(scope, element, attrs){
var text = element.text();
...do some formatting on text....
element.text(text);
}
}
});
In this case the link function will be called for every time angular encounters an element with a formatted-text attribute.
You can even match on other things such as class or element name, by adding a restrict: to your directive. Here is an example of the a few restrict value's and the equivalent jquery selector:
restrict: 'A' => $('[formatted-text]').each(link)
restrict: 'C' => $('.formatted-text').each(link)
restrict: 'E' => $('formatted-text').each(link)
So for example if I wanted to exactly match my initial jquery example above, you would do:
myApp.directive("formattedText", function(){
return {
restrict: 'C',
link: function(scope, element, attrs){
var text = element.text();
...do some formatting on text....
element.text(text);
}
}
});
Hopefully this makes sense and helps de-mistify directives a bit.
What do you want to do with the elements?
Using Angular, DOM manipulation should be reduced as much as possible and be strictly restricted to directives.
Here is some good reading stuff: How do i think in Angular if i have a jQuery background
To actually answer your question, one way to do it would be creating a directive, then access the DOM node inside the directives controller:
app.directive('row',function(){
return{
restrict: 'E',
controller: function($scope,$element,$attrs){
// $element is what you are searching for
}
}
});
If you are trying to vary the text styling according to whether it is in an odd or even row, how about using the ngRepeat and ngClass directives?
In controller:
$scope.items = ["one text", "two text", "three text"];
Template:
<table id="myTable">
<tr ng-repeat="item in items" ng-class="{odd:$odd}"><td>{{item}}</td></tr>
</table>
Stylesheet:
.odd {/* your styling */}

Resources