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 */}
Related
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>
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.
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>
I was reading about ng-transclude in the AngularJS docs on Creating a Directive that Wraps Other Elements and I think I understand properly what it does.
If you have a directive that applies to an element that has content inside it such as the following:
<my-directive>directive content</my-directive>
it will allow you to tag an element within the directive's template with ng-transclude and the content included in the element would be rendered inside the tagged element.
So if the template for myDirective is
<div>before</div>
<div ng-transclude></div>
<div>after</div>
it would render as
<div>before</div>
<div ng-transclude>directive content</div>
<div>after</div>
My question is if it is possible to somehow pass more then a single block of html into my directive?
For example, suppose the directive usage would look like this:
<my-multipart-directive>
<part1>content1</part1>
<part2>content2</part2>
</my-multipart-directive>
and have a template like:
<div>
this: <div ng-transclude="part2"></div>
was after that: <div ng-transclude="part1"></div>
but now they are switched
<div>
I want it to render as follows:
<div>
this: <div ng-transclude="part2">content2</div>
was after that: <div ng-transclude="part1">content1</div>
but now they are switched
<div>
Perhaps I could somehow bind the HTML value of a node to the model so that I will be able to use it in such a way without calling it "transclude"?
Starting Angular 1.5, it's now possible to create multiple slots. Instead of transclude:true, you provide an object with the mappings of each slot:
https://docs.angularjs.org/api/ng/directive/ngTransclude
angular.module('multiSlotTranscludeExample', [])
.directive('pane', function(){
return {
restrict: 'E',
transclude: {
'title': '?pane-title',
'body': 'pane-body',
'footer': '?pane-footer'
},
template: '<div style="border: 1px solid black;">' +
'<div class="title" ng-transclude="title">Fallback Title</div>' +
'<div ng-transclude="body"></div>' +
'<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
'</div>'
};
})
Cool question. I'm not sure there is a built in way, but you can do it yourself in a pretty generic way.
You can access the transcluded element by passing in the $transclude service like this:
$transclude(function(clone, $scope) {
Where clone will be a copy of the pre-linked transcluded content. Then if you label the content in the element like this:
<div id="content">
<div id="content0">{{text}}</div>
<div id="content1">{{title}}</div>
</div>
You can loop over the content and compile it like this:
$scope.transcludes.push($compile(clone[1].children[i])($scope));
Great! now you just need to put the content in the correct place in your template
'<div id="transclude0"></div>' +
'<div id="transclude1"></div>' +
Then you can in your link function assign the content correctly
angular.element(document.querySelector('#transclude' + i)).append(scope.transcludes[i]);
I set up a fiddle you can play with that has this set up.
Hope this helped!
In our project we have modeled multi site trasclusion after JSF 2's ui:composition, ui:insert, ui:define (see ui:composition).
Implementation consists of three simple directives: ui-template, ui-insert, ui-define (see angularjs-api/template/ui-lib.js).
To define a template one writes the following markup (see angularjs-api/template/my-page.html):
<table ui-template>
<tr>
<td ui-insert="menu"></td>
</tr>
<tr>
<td ui-insert="content"></td>
</tr>
</table>
and declares a directive (see angularjs-api/template/my-page.js):
var myPage =
{
templateUrl: "my-page.html",
transclude: true
};
angular.module("app").
directive("myPage", function() { return myPage; });
and finally, to instantiate the directive one needs to write (see angularjs-api/template/sample.html):
<my-page>
<div ui-define="content">
My content
</div>
<div ui-define="menu">
File
Edit
View
</div>
</my-page>
The working sample can be seen through rawgit: sample.html
See also: Multisite Transclusion in AngularJS
I'm trying to create a directive that will work against xml that i am injecting into the dom via a service.
Here is my a relatively reduced example, having removed the async service call for data and also the template: jsBin - transforming elements using directive
Everything works well with regard getting my post elements' header attribute into an h2 tag but it is leaving a element within my page which will fail for some browsers.
to clarify, this is what i get:
<post class="ng-isolate-scope ng-scope" heading="test heading">
<div class="ng-scope">
<h2 class="ng-binding">test heading</h2>
<div>test</div>
</div>
</post>
and this is what i would expect:
<div class="ng-scope">
<h2 class="ng-binding">test heading</h2>
<div>test</div>
</div>
I think Adam's answer is the better route, but alternatively and easier if you include jquery you can do this in your link function:
var e =$compile(template)(scope);
element.replaceWith(e);
You aren't using template correctly in your directive. Your link function should not applying your template as you are in the example code.
var linker = function(scope, element, attrs) {
var template = getTemplate();
element.html(template);
$compile(element.contents())(scope);
};
Instead of that, just do this:
return {
restrict: 'E',
replace: true,
scope: {
heading: '#'
},
template: '<div><h2>{{heading}}</h2><div>test</div></div>'
};
In your post directive. 'replace: true' will not impact anything if you are compiling and manipulating the DOM yourself.
At the same time, though, I have no idea what your compile directive is for and why you have a factory that returns an HTML string. All of that code looks like it could be reduced to a single directive. I can't really comment on what you're trying to do, but once you start using $compile all over the place, odds are you aren't doing things the 'Angular way'.