CONTEXT
I need to load in my AngularJS (v1.4) app some HTML gotten from backend and insert it (the html) into my partial (already loaded). The partial has already some HTML loaded (and completely functional). Right now I'm able to load the HTML and compile it with a directive posted here (Compiling dynamic HTML strings from database). See code below.
PROBLEM
But...when part of the HTML is already loaded (partial loaded and functional) and then I get another HTML content from backend, and the directive is compiling that new one, the entire document (DOM) gets "freezed". I can't type on inputs or do any click on buttons, including those in my previous loaded HTML.
QUESTION
How could I load HTML content, $compile it in "background" or any other way that allows me to continue using the rest of the (already functional) HTML?
It is for me a requisite that the new html content that arrives gets compiled because it contains angular validations and so on that need to be compiled and get inside the "angular world" (be inside the angular digest cycle and so on).
This is the directive I'm using for compiling the html
(function () {
var dynamic = function($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
if (html) {
ele.html(html);
$compile(ele.contents())(scope);
}
});
}
};
};
dynamic.$inject = ['$compile'];
angular.module('app')
.directive('dynamic', dynamic);
}());
In the controller I've something like
// this will be filled with asynchronous calls were I get the HTMLs from a service
// in order to keep this example simple I just made a demo, not with the real async calls
$scope.secciones = []
//when the promises are getting resolved "secciones" would be something like (more items can be added after in time)
$scope.secciones = [
{html: "<div> some html content here (not too small sometimes) </div>"},
{html: "<div> another html content here (not too small sometimes) </div>"}
]
...and in the view
<!--every time an async call with html is resolved, it's added to secciones, then a new div is generated and compiled-->
<!-- if there was some html previously rendered and the app starts compiling new html the UI gets "freezed"-->
<div ng-repeat="item in secciones">
<div dynamic="item.html"></div>
</div>
Note: I'm using this approach because each html represents a tab in a tabpanel I have, in which the user actually sees only one html of all of them in "secciones" (the others are hidden, but still there), but I need to compile the others in order to get them ready for the user when he/she click that other tab (another html in secciones).
If there could be any solution to this by upgrading to a newer version of AngularJS(1.x), let's say 1.6, for instance. I'd would be glad to try it out.
Basically I have done this by getting html from script tag and compile it and append to existing div.
You can use following snippet.
Include div in your html
<div id="step-container">
</div>
Controller code
var template = $templateCache.get('basicInfo'); // or your html
$compile($("#step-container").html(template).contents())($scope);
$("#step-container").show();
For demonstration this will be included in html page
<script type="text/ng-template" id="basicInfo"></script>
In this way you can compile you html coming from backend.
Hope it helps.
You can try use this directive to compile the HTML code. This directive compile the code when to detect any change in variable htmlCode
module.directive('bindHtmlCompile', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function () {
return scope.$eval(attrs.bindHtmlCompile);
}, function (value) {
element.html(value && value.toString());
var compileScope = scope;
if (attrs.bindHtmlScope) {
compileScope = scope.$eval(attrs.bindHtmlScope);
}
$compile(element.contents())(compileScope);
});
}
};
}]);
you can install via bower, DirectiveRepo
USAGE:
<div bind-html-compile="htmlCode"></div>
Related
I have a directive coming in from a database. Similar to how shortcodes work in Wordpress, the user needs to be able to insert these custom galleries here and there. I've tried several, several ways. They compile but they do not affect the HTML on the page. What am I missing?
Incoming from database:
<p>Lorem Ipsum. Some content here</p>
<div work-gallery friendly="bq"></div>
I would like to replace that DIV with the "work-gallery" attribute with a template. When I load that content into the scope of my page, I compile it so my directive triggers.
From controller
$compile($scope.page.content)($scope);
This triggers the directive, it compiles right up to where it needs to append it or replace it and then just doesn't show up on the frontend. If I add that DIV outside of the dynamically loaded content, it works.
Directive
app.directive('workGallery', function ($compile) {
var template = '<div>TEST{{page.med.length}}</div>';
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.page.med = [1, 2];
scope.$watch(attrs.friendly, function () {
ele.html(template);
$compile(ele.contents())(scope);
console.log(ele);
ele.append(ele.contents());
});
}
};
});
Inspecting "element" reveals TEST2 if I dig through down to innerText but that's all I get. Any help is appreciated! From what I understand, this should add "TEST2" within my content.
Expected Output
<p>Lorem Ipsum. Some content here</p>
<div work-gallery friendly="bq">TEST2</div>
Any help is appreciated!
i think there is something wrong about watching an attribute, try:
attrs.$observe('friendly', function(val){
/* .... */
});
I have a angular directive:
app.directive('templater', function() {
return{
restrict: 'E',
templateUrl: '../../tmpl/themer.html',
link: function(scope, element, attrs){
// execute metisMenu plugin
setTimeout(function(){
$(element).templater();
}, 1);
}
}
});
The intent is to push the html from themer.html into a main page.
Right now inside my my_profile.html I have the tag <templater></templater>.
Now, the html displays perfectly, however the css and js are non-operational. THe css tags in the template referenced by the directive affect and are affected by the same js and css files associated with the parent document.
How do I tell the directive to enforce the rules of the parent file on the inserted file?
Thanks
Use $timeout of angular instead of the setTimeOut.
Inject the $timeout dependency in directive
You will not load css & js files from the partial view, as the link & script tag will not loaded. Partial will only load the html from it. If you want to make them working refer this answer
Side Note
You should $timeout instead of setTimeout, that will ensure your angular element has updated with binding on html.
// execute metisMenu plugin
$timeout(function(){
$(element).templater();
}, 1);
In an Angular (1.3) app, I am displaying list of records using ng-repeat. There is a directive with a template inside the ng-repeat. Within the template I'm using ShareThis controls which are activated after the DOM is loaded.
On initial load of the app, the ShareThis Javascript works correctly and activates the buttons. On route change it does not activate. I've found to references to activate the controls manually via stButtons.makeButtons() or stButtons.locateElements();, but I'm unsure where to call this function in the directive or page cycle. I've tried within:
the directive link function - using $timeout or scope.$watch
the template <script>stButtons.locateElements();</script> - activates before model binding
the controller after binding - activates before DOM rendered
My understanding is the function to activate needs to be called after binding and after DOM rendering, but Angular does not know when the DOM is ready. There is a method to dynamically render the ShareThis controls using only Javascript, but I want the HTML defined in the template not Javascript for this case.
I've seen several questions out there related, but none of the answers seem to work 100% for my scenario (and many are broken as of Angular 1.3).
item-list.html (view)
<div ng-repeat="item in vm.itemList">
<item-directive item="item"></item-directive>
</div>
item-list.cs (controller)
{ ... vm.itemList = getItems(...) ... }
item-directive.js (directive)
(function () {
angular.module('app');
function itemDirective() {
var directive = { templateUrl: 'item.html', link: linkFunc, controller: ItemDirective };
return directive;
function linkFunc(scope, element, attr, ctrl) { var item = scope.item }
}
ItemDirective.$inject = ['$scope'];
function ItemDirective($scope) { ... }
}
item.html (directive template)
...
<div class="item-share-section">
<span class='st_sharethis_large' st_url="{{vm.item.url}}" st_title="{{vm.item.name}}"></span>
</div>
...
if I understood well, you want to call the function after the dom is completely render, right? Try this inside the postLink of your directive:
$scope.$watch('viewContentLoaded', stButtons.locateElements())
While my solution is a little hackish, I still prefer it over using $watch, since that is inefficient. Instead, I initialize the function which loads the buttons when the particular view you want to load the buttons with is rendered. The technique is as follows:
Here is the function which you should put in your controller:
$scope.loadShareThis = function() {
stButtons.makeButtons();
}
You'd then add to your item-list.html as such:
<div ng-repeat="item in vm.itemList" ng-init="loadShareThis()">
<item-directive item="item"></item-directive>
</div>
The dynamic URL's might give you additional problems, but that's another issue all together.
I have a web page that uses a directive called custom-html which loads a HTML web url into the page (allowing for sub-templates etc). I have this system configured so that it registers properly with $scope but I seem to be having issues to get JQuery listeners to work with it.
For instance I have the following at the bottom of one of my templates (where the custom-html tag is used above this point)
$(function() {
$(".datepicker").datepicker({ dateFormat: 'yy-mm-dd' });
});
The datepicker never works in the sub-templates I am including via the custom-html directive though. Does anyone have an idea on how I can remedy this? Below is the directive I am using:
backendApp.directive('customHtml', function($compile, $http){
return {
link: function($scope, element, attrs) {
$http.get(attrs['url']).then(function (result) {
element.html(result.data);
element.replaceWith($compile(element.contents())($scope));
});
}
}
});
Using JQuery 1.11.1 and AngularJS 1.2.22 at the moment. Thanks.
EDIT: My apologies, let me clarify the issue that I am having is that when i click on the .datepicker fields that are being inserted via the custom-html directive that it's not working. E.g. it's not opening up the JQuery datepicker as it should be when I click on the input field. When I do this with the regular HTML (not the custom-html) it works just fine.
The problem is that you need to run the datepicker() init AFTER the element gets compiled. So you should do it after you replace the actual html. The code you have above runs one time on page load (or wherever it is) and won't create datepickers for html elements created AFTER that point.
backendApp.directive('customHtml', function($compile, $http){
return {
link: function($scope, element, attrs) {
$http.get(attrs['url']).then(function (result) {
element.html(result.data);
element.replaceWith($compile(element.contents())($scope));
// DO DATEPICKER INIT HERE ON NEW ELEMENT
});
}
}
});
The better way to do it is a datepicker directive so you know the datepicker element has been compiled before you init it (in the link function)
backendApp.directive('myDatepicker', function($compile, $http){
return {
link: function($scope, element, attrs) {
// ONLY JOB IS INITING DATE PICKER HERE
}
}
});
I've created three plunkrs to illustrate my problem. I'm trying to create an AngularJS Directive that will initialize foundation and apply the necessary javascript to the loaded template. At first I was trying to use ngInclude to add the Foundation 5 nav bar to all of the pages of my website. The top bar works as expected when the html is directly applied to a partial. When the html is added in a directive, such as ngInclude, the top bar looses all its functionality. I suspect that this was because foundation is not getting initialized after the template is added by the directive. As a solution I created a custom directive that would initialize foundation and compile the html template. Initializing foundation the way I do freezes the application. Anyone have a solution to this?
Trying to achieve this without resorting to Angular UI.
Example 1: HTML directly applied to the view. Works as expected, when you click on the menu dropdown the pages are displayed.
http://plnkr.co/edit/aQc6j2W9MpRuJo822gAF?p=preview
Example 2: ngInclude used to load template to dom. No functionality is achieved, when you click on the menu dropdown nothing happens.
http://plnkr.co/edit/fSS3FfYKFilMXsIkYUHg?p=preview
Example 3: Created separate directive to replace ngInclude that would initialize foundation, compile, and load the template to DOM. Can't provide a plunkr because it would just freeze up, but here is the code.
.directive('testdirective', function($compile) {
return {
restrict: 'AE',
templateUrl: 'partials/includes/nav.html',
link: function(scope, element, attrs) {
$compile($(document).foundation())(scope);
}
}
})
applied in partial by:
<div testdirective></div>
Do this:
link: function(scope, element, attrs) {
$compile(element.contents())(scope);
$(document).foundation();
}
If you compile the element itself, you create an infinite loop:
$compile(element)(scope); //fail
Always be sure that you only compile the element's contents:
$compile(element.contents())(scope); //win
It seems that you are compiling the whole document and creating the infinite loop.
You can probably just do this:
templateUrl: 'partials/includes/nav.html',
compile: function() {
$(document).foundation();
}
because the template will be automatically compiled so you don't have to do it manually.
Note: it's best practice to inject and use Angular's $document, which is a wrapper for document that helps in testing. $($document).foundation();