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>
Related
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 have a directive foo whose template includes a list via ng-repeat:
<div>
<h5>I want to insert transcluded template into body of the li:</h5>
<ul>
<li ng-repeat='item in items'>
<!-- need item template here -->
</li>
</ul>
</div>
I want the template for each item to (optionally) to be specifiable at the point of usage of the directive:
<foo items='people'>
<h5>{{item.name}}</h5>
</foo>
<foo items='people'>
{{item.name}} is {{item.age}} years old.
</foo>
So I need the innerHTML of the directive (e.g. <h5>{{item.name}}</h5>) to be copied to the marked location in the directive template.
<ng-transclude> does this, but it gives the transcluded items the scope of the directive rather than the item. I also need to be able to optionally pull the item template from somewhere else. So I need to do the transclusion manually.
I have access to the transcluded content during link:, but at that point the list item in question is gone!
<div>
<h5>I need to insert transcluded template into body of the li:</h5>
<ul>
<!-- ngRepeat: item in items -->
</ul>
</div>
I think I need to do it during compile, but the transclusion function passed to the compile function is deprecated.
Found a way to do it with a second directive, but that seems unnecessary...
You can achieve that using $interpolate service and changing a bit your approach please see demo here http://plnkr.co/edit/bSb7fEWiMTdNVJYyiXD8?p=preview
set your dynamic template as attribute in directive.
<foo template="'<h1>{{item.name}}</h1>'" items="people"></foo>
and change your directive to :
app.directive('foo', function($interpolate) {
return {
scope: {
items: '=',
template:'='
},
restrict: 'E',
transclude: true,
templateUrl: 'foo-template.html',
link: function(scope, element, attributes, controller, transclude) {
//interpolate your template like below
scope.getValue= function(item) {
var exp = $interpolate(scope.template);
var result =exp({item:item})
return result;
}
}
}
});
and in your template use ng-bind-html
<li ng-repeat='item in items'>
<div ng-bind-html="getValue(item)"></div>
</li>
don't forget to add ngSanitize module to your app
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
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 */}
I try to build a accordion with Angular UI Bootstrap (http://angular-ui.github.io/bootstrap/#/accordion). On How do I set model according to chosen accordion group. UI Bootstrap i found a working solution to use a template.
In my code i add the template with <script type="text/ng-template" id="template/accordion/accordion-group.html">
In this template a can use {{heading}} set by <accordion-group heading="{{group.title}}" content="{{group.content}}" ng-repeat="group in groups"></accordion-group>.
But how do i set other custom template variables?
I tried to set content="{{group.content}}" in the accordion tag too. Even if set, i don't know how to use it, tried {{content.group}} {{content}} and {% content %}.
Complete code on: http://plnkr.co/dSIVGg64vYSTAv5vFSof
See the edited plunker: http://plnkr.co/edit/8YCUemqILQy3knXqwomJ
You were trying to the nest a controller within the template of a directive. I might be mistaken, but that doesn't work or at least not in a very nice manner.
Instead of nesting controllers I would recommend converting CollapseDemoCtrl into a directive too. You can then pass the {{group.content}} or any other content to this directive.
EDIT: Example of how to pass data to directive scope
The HTML will be something like this:
<span ng-repeat="group in groups">
<div testDirective content="group.content" title="group.title"> </div>
</span>
The directive will look something like this:
angular.module('testModule', [])
.directive('testDirective', function(){
return {
restrict: 'A',
scope: {
content:'=content',
title: '=title'
},
template: '<h1>{{title}}<h1> <div>{{content}}</div>',
link: function(scope, element, attrs) {
}
}
});