angular directives inside ng-bind-html is not evluated - angularjs

This is a subsequent question to this post .
I have an ng-attr-title used in the html injected using ng-bind-html which is not working ie) the title is not formed in the DOM element hence on hovering the tooltip is not formed.here is my code
myApp.controller("MyCtrl",function($scope) {
$scope.tl="this is title";
$scope.level = "<span ng-attr-title='{{tl}}'><b>data</b></span>";
});
Problem is illustrated in the Jsfiddle

You have to use $compile service to achieve this.
JS:
var myApp = angular.module('myApp', ['ngSanitize']);
myApp.controller("MyCtrl", function($scope){
$scope.tl="this is title";
$scope.level = "<span ng-attr-title='{{tl}}'><b>data</b></span>";
});
myApp.directive('compileHtml', compileHtml);
function compileHtml($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function () {
return scope.$eval(attrs.compileHtml);
}, function (value) {
element.html(value);
$compile(element.contents())(scope);
});
}
};
}
HTML:
<div ng-controller="MyCtrl" id="tableForVxp" class="dataDisplay2">
<span compile-html="level" ></span>
</div>
This compileHtml directive will compile your HTML template against your $scope.

I found that the other answers have problems and that the following directive works, and can be installed with bower.
https://github.com/incuna/angular-bind-html-compile

ng-bind-html will inject html as string. It will not compile it.
check http://plnkr.co/edit/M80zp3o4FIODIXFWVAuM?p=preview
angular.module('bindHtmlExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.val = 'patel';
$scope.myHTML =
'I am an <code>HTML</code>string with ' +
'links! and other <em>stuff</em> {{val}}';
}]);
You need custom directive which compiles your html and injects it into your element.
Use following directive
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);
$compile(element.contents())(scope);
});
}
};
}]);

This is a normal behaviour of ng-bind-html. Generally, the controller's code should not have HTML markup - move it to template and controls it's visibility with ng-show/ng-hide instead.
However, you still can do that if you want, just use $compile service. See example here: https://docs.angularjs.org/api/ng/service/$compile

Related

How can I replace link hrefs with ngClicks in dynamically generated HTML within AngularJS?

I'm consuming dynamically generated HTML from an API which may contain hyperlinks, and I'm wanting to replace the hrefs within them with ngClicks. The following directive appears to modify the HTML as intended when I check it in a DOM inspector, but clicking it does nothing. What am I doing wrong?
app.directive('replaceLinks', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function(scope) {
return scope.$eval(attrs.replaceLinks);
}, function(value) {
element.html(value);
angular.forEach(element.contents().find("a"), function(link) {
link.removeAttribute("href");
link.removeAttribute("target");
link.setAttribute("ng-click", "alert('test')");
});
$compile(element.contents())(scope);
});
}
};
}]);
Instead of removing the href please set it to blank (this will preserve the link css), also the ng-click calling the alert can be done by calling the alert('test') inside of a scope method, why the alert didn't fire, is explained in this SO Answer, please refer the below sample code!
// <body ng-app='myApp' binds to the app being created below.
var app = angular.module('myApp', []);
app.directive('replaceLinks', ['$compile', function($compile) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.forEach(element.find("a"), function(link) {
link.setAttribute("href", "");
link.removeAttribute("target");
link.setAttribute("ng-click", "scopeAlert('test')");
});
$compile(element.contents())(scope);
}
};
}]);
// Register MyController object to this app
app.controller('MyController', ['$scope', MyController]);
// We define a simple controller constructor.
function MyController($scope) {
// On controller load, assign 'World' to the "name" model
// in <input type="text" ng-model="name"></input>
$scope.name = 'World';
$scope.scopeAlert = function(name) {
window.alert(name);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller='MyController' ng-app="myApp">
<div replace-links>
test 1
test 2
test 3
</div>
</div>

Angular directive is not working if pass with html string and parse with ng-bind-html

I am creating a simple angular directive for reference. When I add it to the HTML page it is working fine but when I add it through HTML tags string and parse with ng-bind-html then it is not working. Please suggest some approach.
Directive:
<notification message="{{message}}"></notification>
//When pass through controller object, It's not working
$scope.htmlContent = '<p>Hello</p><notification message="{{message}}"></notification>';
Plunker
You need to call a compile directive to achieve this functionality
JS Code
var app = angular.module('plunker', ['ngRoute', 'ui.bootstrap',]);
app.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
app.controller("MessageCtrl", function($scope) {
$scope.message = "Product created!";
$scope.htmlContent = '<notification message="{{message}}"></notification>';
})
app.directive("notification", function() {
return {
restrict: 'E',
scope: {
message: '#'
},
template: '<div class="alert">{{message}}</div>'
}
});
app.directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
}]);
HTML code
<div ng-controller="MessageCtrl">
<div compile="htmlContent"></div>
<notification message="{{message}}"></notification>
</div>
Check Plunker
You can read more about this in this answer

Get transcluded text in directives controller

I can get some text from my directive into my directives controller like this:
The html:
<my-directive text="Some text"></my-directive>
In the directive, I can get hold of the text like this:
bindToController: {
text: "#"
};
And I could use it like this in the directive's controller:
controller: function() {
this.textUpperCase = this.text.toUpperCase();
}
But how could can I get hold of the text in the directives controller via transclusion? So that I can have the html like this:
<my-directive>Some text</my-directive>
As mentioned in the comments you could use element.html() or transclusion.
But I would prefer transclusion because that's easier to work with the data. You can use $transclude in your controller or transcludeFn in compile or link method.
Here I think the best would be the controller.
Please have a look at this fiddle or the demo below.
I think injecting the $element into controller won't work becasue you would get the uncompiled template with-out the data you're looking for.
angular.module('demoApp', [])
.controller('mainCtrl', function($scope) {
$scope.hello = 'hello world from scope';
})
.directive('upperCase', function() {
return {
restrict: 'E',
transclude: true,
scope: {
},
template: '<div>{{text}}</div>',
controller: function($scope, $transclude, $element) {
$transclude(function(clone, scope) {
//console.log(clone.text(), scope.hello);
console.log(clone);
$scope.text = clone.text().toUpperCase();
//transcludedContent = clone;
//transclusionScope = scope;
});
//console.log($element.html()); // not working will get the uncompiled template from directive
console.log($scope.text); // can be used here too
},
link: function(scope, element, attrs, ctrl, transclude) {
var text = element.html();
//console.log(transclude);
//element.html(text.toUpperCase()); // also working (add ng-transclude to your template)
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainCtrl">
<upper-case>hello world</upper-case>
<upper-case>hello angular</upper-case>
</div>

What happens to ng-click and controller access when added by a directive's link function

I have a simple JSFiddle:
<div ng-controller="MainCtrl as mainCtrl">
<div ng-show="mainCtrl.visible" ng-click="mainCtrl.swap()">Can you see me?</div>
<div see-me></div>
</div>
angular.module('AngularTestApp', [])
.controller('MainCtrl', [function () {
var self = this;
self.visible = true;
self.swap = function() {
self.visible = ! self.visible;
};
}])
.directive('seeMe', [function () {
return {
template: 'or me?',
link: function (scope, element, attrs) {
attrs.$set('ng-show', 'mainCtrl.visible');
attrs.$set('ng-click', 'mainCtrl.swap()');
}
};
}]);
Since the default value for a scope on a directive's definition object is false I would expect the parent's scope to be available and thus for attrs.$set('ng-click', 'mainCtrl.swap()'); to work, but it does not fire when I click the div. Why?
(N.B. I tried adding $compile as per ppa's answer to 'AngularJS - ng-click in directive's link function' but that had no effect.)
Setting attributes on an element doesn't process any directives. You'll have to use the $compile service to compile the new template with the current scope and replace the current element with the compiled element.
.directive('seeMe', ['$compile', function ($compile) {
return {
template: 'or me?',
link: function (scope, element, attrs) {
var newElement = $compile('<div ng-show="mainCtrl.visible" ng-click="mainCtrl.swap()">or me?</div>')(scope);
element.replaceWith(newElement);
}
};
JSFiddle
In the question I mention trying $compile but I think I messed up injection when I tried that. c.P.u.1's answer show's how I should have done it. Here's a version of c.P.u.1's answer which does not replace the current element but compiles it after the additional attributes are added. Note that I need to remove the original directive attribute avoid infinite compile loop (see Ilan Frumer's answer to 'Angular directive how to add an attribute to the element?').
HTML
<div ng-controller="MainCtrl as mainCtrl">
<div ng-show="mainCtrl.visible" ng-click="mainCtrl.swap()">Can you see me?</div>
<div see-me></div>
</div>
JavaScript
angular.module('AngularTestApp', [])
.controller('MainCtrl', [function () {
var self = this;
self.visible = true;
self.swap = function() {
self.visible = ! self.visible;
};
}])
.directive('seeMe', ['$compile', function ($compile) {
return {
template: 'or me?',
link: function (scope, element, attrs) {
element.removeAttr('see-me');
attrs.$set('ng-show', "mainCtrl.visible");
attrs.$set('ng-click', "mainCtrl.swap()");
$compile(element)(scope);
}
};
}]);
(JSFiddle here).

Does dynamically adding ng-bind directive not work?

I'm adding the attribute ng-bind='data' to an element through a directive
myApp.directive('myDiv', function() {
return {
restrict: 'E',
link: function($scope, element, attrs) {
element.html('<div ng-bind="data">me</div>');
} }; });
function MyCtrl($scope) {
$('#click').click(function() {
$scope.data = 'change';
}); }
but the ng-bind isn't working as expected.
http://jsfiddle.net/HB7LU/3427/
To answer the main question your issue here is that if you want to include bindings in your template you need to compile the element. The syntax for that is something like:
$compile(angular.element("my html"))(scope)
In your case that actually ends up looking like:
myApp.directive('myDiv', function($compile) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// here adding the ng-bind dynamically
element.html($compile(angular.element('<div ng-bind="data">me</div>'))(scope));
}
};
});
To see it working checkout the updated fiddle here: http://jsfiddle.net/CC8BK/.
One other note is you are using jQuery's "click" event to change scope values. When working with angular you need to start by trying not to use jQuery and instead using the angular directives for whatever you can. In your case ng-click is the directive you should be using. I inserted this in your html so you could see what it would look like.
Hope this puts you on the right track. Best of luck!
As #drew_w said you have to compile element using $compile if you need to apply from link,
or else you can use template in directure like
template: '<div ng-bind="data"></div>'
I mostly prefer template
Also don't use jquery function like
$('#click').click(function() {
$scope.data = 'change';
});
instead you can use
$scope.change = function()
{
$scope.data = 'change';
}
or
ng-click="data = 'change'"
as #drew_w said
Take a look the full code
Working demo
html
<div ng-controller="MyCtrl">Hello, {{name}}!
<button id='click' ng-click="change()">click to 'change'</button>
<my-div>watch, this doesn't change!!!???</my-div>
</div>
script
var myApp = angular.module('myApp', []);
myApp.directive('myDiv', function ($compile) {
return {
restrict: 'E',
template:'<div ng-bind="data"></div>'
};
});
myApp.controller('MyCtrl', function ($scope) {
$scope.data = "me";
$scope.name = 'Superhero';
$scope.change = function () {
$scope.data = 'change';
}
});
here's a variation of the above answer using the Template property and using a click function:
myApp.directive('myDiv', function() {
return {
restrict: 'E',
template:'<div ng-bind="data"></div> me'
};
});
and on the controller:
$scope.click = function() {
$scope.data = 'change';
};
and on the View
<button ng-click="click()">click to 'change'</button>
http://jsfiddle.net/HB7LU/3446/

Resources