I need to write directive to append html in div
Here i want to append html which i get it from server usinh $http post request
<div id="invoice_template_preview"
ng-bind-html-unsafe="invoice_html_template"
class="span6"
style="background: rgba(242, 230, 205, 0.95);margin: -100px 0 0 0;border: 1px solid #ddd; height: auto;padding: 18px;position: relative;width: 50% !important;">
</div>
This is my angular function to get html from db
$scope.getInvoiceTemplate = function() {
$scope.invoiceTemplate = [];
var request = $http({
method: "post",
url: "/c_make_invoice/",
data: {
action: 'getInvoiceTemplate',
id:$scope.our_company_id
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
request.success(function (data) {
$scope.invoiceTemplate = data.result;
$scope.invoice_html_template = $scope.invoiceTemplate.invoice_html_template;
$scope.invoice_num_format = $scope.invoiceTemplate.invoice_num_format;
});
};
I try this
$scope.invoice_html_template = $scope.invoiceTemplate.invoice_html_template;
but its not a proper way to solve this.
I return json from server, how i can append that html in #invoice_template_preview
Updated directive to watch the scope variable. When it changes the template html is changed to whatever is in the variable.
.directive('CustomDirective', function(){
return {
restrict: 'E',
link: function(scope, element, attrs){
scope.$watch('invoice_html_template', function(){
var template = scope.invoice_html_template;
element.html(template);
$compile(element.contents())(scope);
});
},
template: '<div>{{$scope.invoiceTemplate}}</div>'
});
and it can be used:
<div id="invoice_template_preview">
<custom-directive></custom-directive>
</div>
You should also be using $sce when getting hold of the HTML. see sce
request.success(function (data) {
$scope.invoiceTemplate = $sce.trustAsHtml(data.result.invoice_html_template);
Whan i try $compile I get that $compile is not defined, I fixed that whan I add $compile param in controller initialization.
After that I simple add this code
$('#invoice_template_preview').html($compile($scope.invoiceTemplate.invoice_html_template)($scope));
Related
I have a directive that should aplly on each 'select' tag, but because it parse the select data it should be done after the data loded.
The following code i wrote work just on the last select item:
myApp.directive('optionsClass', function ($parse) {
return {
require: 'select',
link: function ($scope, elem, attrs, ngSelect) {
$scope.updateLang = function () {
// Here i perform data parsing
}
}
};
});
controller:
myApp.controller('myCtrl', function ($scope, $http) {
$scope.loadData = function () {
$http({
url: 'x',
method: 'POST',
data: { data: $scope.data}
}).then(function(response) {
$scope.updateLang();
}
});
}
You can do the following -
function loadData(){
$scope.loading = true; // additional variable to keep track if data is loading or not
someService.getData()
.then(function(success){
// populate success.data in your model property
})
.finally(function(){ $scope.loading = false; // loading is complete)
}
And in your template you can wrap the whole thing inside a div with an ng-if-
<div ng-if="!loading">
// your code here
</here>
in this way, while loading is true the inner html of the above div will not be compiled. And when the loading is false, then it is for sure that your data is loaded and then your inner html code will be compiled.
I want to use ng-repeat to show more then 100 images in a page. Those images are taking significant time in loading and i don't want to show them getting loaded to the users. So, I only want show them after all of them are loaded in the browser.
Is there a way to detect, if all the images are loaded?
you can use load event like this.
image.addEventListener('load', function() {
/* do stuff */
});
Angular Directives
Solution for single image
HTML
<div ng-app="myapp">
<div ng-controller="MyCtrl1">
<loaded-img src="src"></loaded-img>
<img ng-src="{{src2}}" />'
</div>
</div>
JS
var myApp = angular.module('myapp',[]);
myApp
.controller('MyCtrl1', function ($scope) {
$scope.src = "http://lorempixel.com/800/200/sports/1/";
$scope.src2 = "http://lorempixel.com/800/200/sports/2/";
})
.directive('loadedImg', function(){
return {
restrict: 'E',
scope: {
src: '='
},
replace: true,
template: '<img ng-src="{{src}}" class="none"/>',
link: function(scope, ele, attr){
ele.on('load', function(){
ele.removeClass('none');
});
}
};
});
CSS
.none{
display: none;
}
http://jsfiddle.net/jigardafda/rqkor67a/4/
if you see the jsfiddle demo, you will notice src image is only showing after image is fully loaded whereas in case of src2 you can see image loading.(disable cache to see the difference)
Solution for multiple images
HTML
<div ng-app="myapp">
<div ng-controller="MyCtrl1">
<div ng-repeat="imgx in imgpaths" ng-hide="hideall">
<loaded-img isrc="imgx.path" onloadimg="imgx.callback(imgx)"></loaded-img>
</div>
</div>
</div>
JS
var myApp = angular.module('myapp',[]);
myApp
.controller('MyCtrl1', function ($scope, $q) {
var imp = 'http://lorempixel.com/800/300/sports/';
var deferred;
var dArr = [];
var imgpaths = [];
for(var i = 0; i < 10; i++){
deferred = $q.defer();
imgpaths.push({
path: imp + i,
callback: deferred.resolve
});
dArr.push(deferred.promise);
}
$scope.imgpaths = imgpaths;
$scope.hideall = true;
$q.all(dArr).then(function(){
$scope.hideall = false;
console.log('all loaded')
});
})
.directive('loadedImg', function(){
return {
restrict: 'E',
scope: {
isrc: '=',
onloadimg: '&'
},
replace: true,
template: '<img ng-src="{{isrc}}" class="none"/>',
link: function(scope, ele, attr){
ele.on('load', function(){
console.log(scope.isrc, 'loaded');
ele.removeClass('none');
scope.onloadimg();
});
}
};
});
To detect if all images are loaded,
for each image i generated a deferred object and passed its deferred.resolve as a image onload callback of the directive and then pushed that deferred objects promise in an array. and after that i used $q.all to detect if all those promise are yet resolved or not.
http://jsfiddle.net/jigardafda/rqkor67a/5/
UPDATE: angular way added.
UPDATE: added solution for loading multiple images.
Check if all images are loaded
jQuery.fn.extend({
imagesLoaded: function( callback ) {
var i, c = true, t = this, l = t.length;
for ( i = 0; i < l; i++ ) {
if (this[i].tagName === "IMG") {
c = (c && this[i].complete && this[i].height !== 0);
}
}
if (c) {
if (typeof callback === "function") { callback(); }
} else {
setTimeout(function(){
jQuery(t).imagesLoaded( callback );
}, 200);
}
}
});
Callback occurs when all images are loaded
image load errors are ignored (complete will be true)
Use:
$('.wrap img').imagesLoaded(function(){
alert('all images loaded');
});
Note : this code worked for me, Source :
http://wowmotty.blogspot.in/2011/12/all-images-loaded-imagesloaded.html
I want to be able to load the directive's template from a promise. e.g.
template: templateRepo.get('myTemplate')
templateRepo.get returns a promise, that when resolved has the content of the template in a string.
Any ideas?
You could load your html inside your directive apply it to your element and compile.
.directive('myDirective', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
//Some arbitrary promise.
fetchHtml()
.then(function(result){
element.html(result);
$compile(element.contents())(scope);
}, function(error){
});
}
}
});
This is really interesting question with several answers of different complexity. As others have already suggested, you can put loading image inside directive and when template is loaded it'll be replaced.
Seeing as you want more generic loading indicator solution that should be suitable for other things, I propose to:
Create generic service to control indicator with.
Manually load template inside link function, show indicator on request send and hide on response.
Here's very simplified example you can start with:
<button ng-click="more()">more</button>
<div test="item" ng-repeat="item in items"></div>
.throbber {
position: absolute;
top: calc(50% - 16px);
left: calc(50% - 16px);
}
angular
.module("app", [])
.run(function ($rootScope) {
$rootScope.items = ["One", "Two"];
$rootScope.more = function () {
$rootScope.items.push(Math.random());
};
})
.factory("throbber", function () {
var visible = false;
var throbber = document.createElement("img");
throbber.src = "http://upload.wikimedia.org/wikipedia/en/2/29/Throbber-Loadinfo-292929-ffffff.gif";
throbber.classList.add("throbber");
function show () {
document.body.appendChild(throbber);
}
function hide () {
document.body.removeChild(throbber);
}
return {
show: show,
hide: hide
};
})
.directive("test", function ($templateCache, $timeout, $compile, $q, throbber) {
var template = "<div>{{text}}</div>";
var templateUrl = "templateUrl";
return {
link: function (scope, el, attr) {
var tmpl = $templateCache.get(templateUrl);
if (!tmpl) {
throbber.show();
tmpl = $timeout(function () {
return template;
}, 1000);
}
$q.when(tmpl).then(function (value) {
$templateCache.put(templateUrl, value);
el.html(value);
$compile(el.contents())(scope);
throbber.hide();
});
},
scope: {
text: "=test"
}
};
});
JSBin example.
In live code you'll have to replace $timeout with $http.get(templateUrl), I've used the former to illustrate async loading.
How template loading works in my example:
Check if there's our template in $templateCache.
If no, fetch it from URL and show indicator.
Manually put template inside element and [$compile][2] it.
Hide indicator.
If you wonder what $templateCache is, read the docs. AngularJS uses it with templateUrl by default, so I did the same.
Template loading can probably be moved to decorator, but I lack relevant experience here. This would separate concerns even further, since directives don't need to know about indicator, and get rid of boilerplate code.
I've also added ng-repeat and run stuff to demonstrate that template doesn't trigger indicator if it was already loaded.
What I would do is to add an ng-include in my directive to selectively load what I need
Check this demo from angular page. It may help:
http://docs.angularjs.org/api/ng.directive:ngInclude
````
/**
* async load template
* eg :
* <div class="ui-header">
* {{data.name}}
* <ng-transclude></ng-transclude>
* </div>
*/
Spa.Service.factory("RequireTpl", [
'$q',
'$templateCache',
'DataRequest',
'TplConfig',
function(
$q,
$templateCache,
DataRequest,
TplConfig
) {
function getTemplate(tplName) {
var name = TplConfig[tplName];
var tpl = "";
if(!name) {
return $q.reject(tpl);
} else {
tpl = $templateCache.get(name) || "";
}
if(!!tpl) {
return $q.resolve(tpl);
}
//加载还未获得的模板
return new $q(function(resolve, reject) {
DataRequest.get({
url : "/template/",
action : "components",
responseType : "text",
components : name
}).success(function(tpl) {
$templateCache.put(name, tpl);
resolve(tpl);
}).error(function() {
reject(null);
});
});
}
return getTemplate;
}]);
/**
* usage:
* <component template="table" data="info">
* <span>{{info.name}}{{name}}</span>
* </component>
*/
Spa.Directive.directive("component", [
"$compile",
"RequireTpl",
function(
$compile,
RequireTpl
) {
var directive = {
restrict : 'E',
scope : {
data : '='
},
transclude : true,
link: function ($scope, element, attrs, $controller, $transclude) {
var linkFn = $compile(element.contents());
element.empty();
var tpl = attrs.template || "";
RequireTpl(tpl)
.then(function(rs) {
var tplElem = angular.element(rs);
element.replaceWith(tplElem);
$transclude(function(clone, transcludedScope) {
if(clone.length) {
tplElem.find("ng-transclude").replaceWith(clone);
linkFn($scope);
} else {
transcludedScope.$destroy()
}
$compile(tplElem.contents())($scope);
}, null, "");
})
.catch(function() {
element.remove();
console.log("%c component tpl isn't exist : " + tpl, "color:red")
});
}
};
return directive;
}]);
````
I am trying to load a chart using angularjs directive. I want to load the chart after data comes from server response. But scope is empty. My code is
<div class="span4" ui-if="loadingIsDone">
<article id="piChart2">
<section id="toolBox1">
<h3 id="toolH1"></h3>
<p id="toolD1"></p>
</section>
<article id="toolTip1"></article>
<canvas id="canvas1" width="250" height="250" draw-chart>
</canvas>
</article>
</div>
and my controller is
controller(){
$scope.teamStrengths = {};
$scope.loadingIsDone = false;
$http({
url: "url",
method: "POST",
data: {"userUids":uids}
}).success(function(response){
$scope.teamStrengths = response.resource.strengths;//data loads successfully
$scope.loadingIsDone = true;
//rest of code is skipped
}
and my directive is
Directives.directive('drawChart', function() {
return function(scope, element, attrs) {
console.debug("element :"+element);
console.debug("attrs :"+attrs);
graphDraw('canvas1',scope.teamStrengths.value1,scope.teamStrengths.value2,scope.teamStrengths.value3)
//grap loads successfully with static data
};
});
kindly help me, i shall be thankful
$http call is asynchronous. Directive need to $watch changes in the scope. Add this in your directive:
scope.$watch('teamStrengths', function(newValue, oldValue) {
if (newValue)
graphDraw('canvas1',scope.teamStrengths.value1,scope.teamStrengths.value2,scope.teamStrengths.value3)
}, true);
Or watch for $scope.loadingIsDone change:
scope.$watch('loadingIsDone', function(newValue, oldValue) {
if (newValue == true)
graphDraw('canvas1',scope.teamStrengths.value1,scope.teamStrengths.value2,scope.teamStrengths.value3)
}, true);
There seems to be a bug where model data fetched from an http call is present in the $scope but not in a directive. Here is the code that illustrates the problem:
Jsfiddle: http://jsfiddle.net/supercobra/hrgpc/
var myApp = angular.module('myApp', []).directive('prettyTag', function($interpolate) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
var text = element.text();
//var text = attrs.ngModel;
var e = $interpolate(text)(scope);
var htmlText = "<b>" + e + "</b>";
element.html(htmlText);
}
};
});
function MyCtrl($scope, $http, $templateCache) {
$scope.method = 'JSONP';
$scope.url = 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero';
$scope.fetch = function () {
$scope.code = null;
$scope.response = null;
$http({
method: $scope.method,
url: $scope.url,
cache: $templateCache
}).
success(function (data, status) {
$scope.status = status;
$scope.data = data;
}).
error(function (data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
}
The HTML
<div ng-controller="MyCtrl">
<h1>Angular $http call / directive bug</h1>
<p>This fiddle illustrates a bug that shows that model w/ data fetched via an http call
is not present within a directive.</p>
<hr>
<h2>HTTP call settings</h2>
<li>Method: {{method}}
<li>URL: {{url}}
<br>
<button ng-click="fetch()">fetch</button>
<hr/>
<h3>HTTP call result</h3>
<li>HTTP response status: {{status}}</li>
<li>HTTP response data: {{data}}</li>
<hr/>
<h2>Pretty tag</h2>
<pretty-tag>make this pretty</pretty-tag>
<hr/>
<h3 style="color: red" >Should show http response data within pretty tag</h3>
[<pretty-tag>{{data}}</pretty-tag>] // <=== this is empty
</div>
Jsfiddle: http://jsfiddle.net/supercobra/hrgpc/
Any help appreciated.
You are replacing the content of the directive in your directive implementation. Since the $http request is async, the directive completes before the data is retrieve and assigned to the scope.
Put a watch on data variable inside the directive and then re-render the content, something like
scope.$watch(attrs.source,function(value) {
var e = $interpolate(text)(scope);
var htmlText = "<b>" + e + "</b>";
element.html(htmlText);
});
Based on #Marks feedback and your request i have update fiddle
http://jsfiddle.net/cmyworld/V6sDs/1/