I know I can apply to the template/templateURL, but currently the content will load from another place in my application.
JSbin: http://jsbin.com/imuseb/1/
HTML
<html ng-app="app">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js"></script>
</head>
<body>
<div alert>here</div>
</body>
</html>
JS code:
var app = angular.module('app', []);
app.directive('alert', function (){
return {
link: function (scope, element, attrs) {
element.on("click", function (){
alert("here");
});
}
};
});
$(function (){
$("body").append("<div alert>here too</div>");
});
The new DOM must be accessible to the angular app in order to be compiled correctly. Here is one way to do this (not the only way). For applying the new DOM to the app's $rootScope, change this:
$(function (){
$("body").append("<div alert>here too</div>");
});
to:
app.run(function($rootScope){
$rootScope.$apply($("body").append("<div editable>here too</div>"));
});
According to the Angular docs:
$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
It would be best if you could load your extra content from within the angular framework. Check out ng-include.
If that can't be used to do what you need, you'll have to manually call the angular compile service on the element, to compile it and link it onto the scope yourself using $compile.
Compiling elements touches the DOM, and therefore should be done within a custom directive if possible.
Related
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>
I've a scenario which I want to run a custom directive on the DOM that ng-bind-htmlcreate.
Basicly I've to customize the behavior of the html tag <a> because the html that is loading is unsafe and when the user click the link I've to execute some functions before anything happens, aka, the click event of jqLite.
So my original ideia was create a directive that modifies the behavior of any <a> inside the container that I put the attribute of my directive.
This works fine, until I combine with ng-bind-html, my directive runs before the ng-bind-html compile the string into html and attached to the DOM.
So, is any way to run my custom directive after the ng-bind-html run?
DEMO
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-bind="sometext" my-directive>before</div>
</div>
Controller:
angular.module('myApp', []);
angular.module('myApp').controller('myCtrl', function($scope) {
$scope.sometext="stuff here";
});
Directive:
angular.module('myApp').directive('myDirective', function() {
return {
priority: 10, // adjust this value ;)
link: function(scope,element,attrs) {
scope.$watch(attrs.ngBind, function(newvalue) {
console.log("element ",element.text());
});
}
};
});
Use priority property inside directive to run your code after mg-bind
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'm trying to integrate translate partial loading into my app, but there is a flicker on initial load of the translation file (when changing states). I'm using translate-cloak as suggested but I still get the same effect.
Note The flicker only happens when when changing the partial, not the language.
Here is my setup
Config
app.config(['$translateProvider', function($translateProvider){
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'dist/locales/{lang}/{part}.json'
});
$translateProvider.preferredLanguage('en-US');
}])
Controller
app.controller('appCtrl', function($translate, $translatePartialLoader){
$translatePartialLoader.addPart('app');
$translate.refresh()
});
Template
<div translate="HEADLINE"></div>
Layout
<html ng-app="app" >
<head>
<title>SportProphecy</title>
<script src="/dist/js/vendors.min.js"></script>
<script src="/dist/js/app.min.js"></script>
</head>
<body ui-view ng-cloak translate-cloak>
</body>
</html>
Am I missing something? I have read through all the documentation out there regarding translate-cloak.
PS: I tried creating a fiddle but for some reason the json files aren't being requested.
This is called "Flash of untranslated content" from the angular translate docs. You might want to check it out here.
Basically, you need to define atleast one language to be loaded synchronously. So go ahead and add translations for your preferred language ('en' in your case) in the app.config
app.config(['$translateProvider', function($translateProvider){
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'dist/locales/{lang}/{part}.json'
});
$translateProvider.preferredLanguage('en-US');
$translateProvider.translations('en-US', {
'HEADLINE': 'My Fancy App!'
});
}])
svikasg is completly right. Though as a workarround you can do the following inside your controller:
$translatePartialLoader.addPart('my-part'); //this should be already in your controller
var vm = this;
function updateCloak () {
vm.cloak = $translate.instant('some_translation_id') === 'some_translation_id';
}
$rootScope.$on('$translateLoadingEnd', function () {
$timeout(updateCloak);
});
updateCloak();
And then in your page container where you're using the translations add an ng-if="ctrl.cloak". You can also use ng-class if you prefer.
Of course you need to inject $rootScope, $translate, $timeout in your controller also.
This code will set a cloak scope property to true as long as an expected translation is not available, allowing you to hide the parts using the translations in the same file than the one you're checking.
Note: if you're not using controllerAs syntax replace all the vm references with scope and remove the ctrl. from the ng-if.
I'm using JQuery UI components in a view of AngularJS/JQuery application.
I need something like this (does not work) in my JavaScript code:
$(document).ready(function () {
var elem = $('div[ng-view]')[0];
var $scope = angular.element(elem).scope();
$scope.$on('$viewContentLoaded', function() {
// multiple JQuery statements
// to support JQuery-UI componenets
});
});
This code is included as <script> into index.html that has <div class="container" ng-view> element.
My thinking was that we need a two-step process:
First JQuery reacts on document-ready HTML event and attaches a listener to Angular's $viewContenLoaded using $scope retrieved using [ng-view] element.
Then each time a view is loaded my JQuery code will be executed and JQuery UI components get activated and wired.
Apparently my logic is flawed somewhere. Please point me in the right direction.
ADDITIONAL INFO (posted 03/31/14):
The rest of my code (controllers, service, routing) is written in TypeScript.
That element needs to be compiled in order to bind angulars scope to that element. You could try something like:
var scope = angular.injector(['ng']).get('$rootScope').$new();
var compile = angular.injector(['ng']).get('$compile');
compile(elem)(scope);
scope.$on('$viewContentLoaded', function(){
// Your code
});
Though I would suggest putting your code in a directive. The code I shown above is nothing more than a hack and dangerous since now you have global access to your services.