AngularJS: Can't call onClick() when using ng-bind-html [duplicate] - angularjs

I've included a Plunker here: http://plnkr.co/edit/4vqV8toHo0vNjtfICtzI?p=preview
I'm trying to add a button to the DOM and when clicked should execute the function bound to it. In this case it should alert "testing". Here is the code.
controller
app.controller('MainCtrl', function($scope, $sce) {
$scope.trustedHtml = $sce.trustAsHtml('<button ng-click="testAlert()">Submit</button>');
$scope.testAlert = function () {
alert('testing')
};
});
HTML
<body ng-controller="MainCtrl">
<div ng-bind-html="trustedHtml"></div>
</body>

$sce.trustAsHtml and ng-bind-html are not meant to build HTML with directives. This technique will not work.
This is because angular works by first compiling and then linking. See the conceptual overview for a good explaination.
In short, by the time you link the HTML defined in your trustAsHtml, it is too late for angular to compile (and therefore understand) the ng-click directive.
In order to dynamically add HTML, you should be looking at the $compile service (and/or directives). Docs are here.

For Angular 1.6.1, I found a solution that worked for me.
template:
<div ng-bind-html="trustAsHtml(content);" init-bind> </div>
In controller:
$scope.trustAsHtml = function(string) {
return $sce.trustAsHtml(string);
};
Directive:
.directive('initBind', function($compile) {
return {
restrict: 'A',
link : function (scope, element, attr) {
attr.$observe('ngBindHtml',function(){
if(attr.ngBindHtml){
$compile(element[0].children)(scope);
}
})
}
};
})

Related

ui-sref not working properly when anchor tag is loaded from properties file

Entry in properties file
MYKEY= <a ui-sref="mystate.state">Go to my page using state</a> and <a href="/#/mypage/page">Go to my page using link/a>
I have fetched this key from properties file & applied $sce.trustAsHtml() on it and used in html. I could see link for Go to my page using link but Go to my page using state not working properly as it has ui-sref tag. Can someone please specify reason why it's not working and solution for this.
Note : ui-sref is working properly in my project when it's directly used in html.
It may be because the content is not compiled, hence ui-sref is not working.
I found the below directive which compiles and inserts html code, please check if this solves your issue!
The Directive was got from this SO Answer
var app = angular.module('myApp', []);
app.controller('MyController', function MyController($scope) {
$scope.test = function() {
console.log("it works!");
}
$scope.html = '<a ui-sref="mystate.state">Go to my page using state</a> and Go to my page using link<button ng-click="test()">click</button>{{asdf}}';
});
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);
}
);
};
}])
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-controller='MyController' ng-app="myApp">
<div compile="html"></div>
</div>

AngularJS1 directive with `terminal:true` will disable rendering of expressions, why?

HTML code:
<div ng-controller="MyController">
<div>{{ hello }}</div>
<div my-terminal>{{hello}}</div>
</div>
JS code:
const app = angular.module('app', [])
app.controller('MyController', function ($scope) {
$scope.hello = 'Hello, AngularJS'
})
app.directive('myTerminal', function () {
return {
restrict: 'A',
terminal: true,
link: function () {
console.log('--- myTerminal')
}
}
})
Please notice the terminal is true.
Result:
From angularjs document, I found when terminal is true, any other directives applied on the same element with lower priority will not be executed, but I can't explain why <div my-terminal>{{hello}}</div> will not render the expression {{hello}}
A small complete demo for this question: https://github.com/freewind-demos/angularjs1-directive-terminal-issue-demo
https://github.com/angular/angular.js/blob/master/src/ng/compile.js :
function addTextInterpolateDirective(directives, text) {
var interpolateFn = $interpolate(text, true);
if (interpolateFn) {
directives.push({
priority: 0,
compile: function textInterpolateCompileFn(templateNode) {
var templateNodeParent = templateNode.parent(),
hasCompileParent = !!templateNodeParent.length;
...
So using expression {{}} results in adding directive. Guess thats why it is affected by 'terminate' property.
From the Docs:
terminal
If set to true then the current priority will be the last set of directives which will execute (any directives at the current priority will still execute as the order of execution on same priority is undefined). Note that expressions and other directives used in the directive's template will also be excluded from execution.
— AngularJS Comprehensive Directive API Reference - terminal
A better explaination of what that means comes from the Docs for ng-non-bindable which uses the terminal property:
ngNonBindable
The ngNonBindable directive tells AngularJS not to compile or bind the contents of the current DOM element, including directives on the element itself that have a lower priority than ngNonBindable. This is useful if the element contains what appears to be AngularJS directives and bindings but which should be ignored by AngularJS. This could be the case if you have a site that displays snippets of code, for instance.
— AngularJS ng-non-bindable Directive API Reference
You need to use ng-bind
<div ng-controller="MyController">
<div>{{hello}}</div>
<div my-terminal ng-bind="hello"></div>
</div>
The DEMO
const app = angular.module('app', [])
app.controller('MyController', function ($scope) {
$scope.hello = 'Hello, AngularJS'
})
app.directive('myTerminal', function () {
return {
restrict: 'A',
terminal: true,
link: function () {
console.log('--- myTerminal')
}
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="MyController">
<div>{{ hello }}</div>
<div my-terminal ng-bind="hello"></div>
</body>

How to bind ng-click to a custom directive and call a parent function?

We are using Angular 1.4.2 and I am trying to take a count value from a directive using ng-click, pass it to a function, then pass it up to the parent controller. After some effort it is working in a plunker, but unfortunately when I tried to move this functionality back into the main code, I'm not able to get a controller to bind to the isolated scope.
Should be simple, but I've tried injecting the current controller into the directive and trying to create a new controller, but nothing happens when I press click on the button.
Here is the code:
TEMPLATE:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.4.2" data-semver="1.4.2" src="https://code.angularjs.org/1.4.2/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div id="app" ng-app="app">
<div ng-controller="mainCtrl">
<my-directive ctrl-fn="ctrlFn(count = count + 10)"></my-directive>
</div>
</div>
</body>
</html>
SCRIPT:
var app = angular.module('app', []);
app.controller('mainCtrl', function($scope){
$scope.count = 0;
$scope.ctrlFn = function() {
console.log('In mainCtrl ctrlFn!');
//$scope.count += 10; Old hardcoded value.
console.log("count is: " + JSON.stringify($scope.count));
//Call service here
};
});
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
'ctrlFn' : '&'
},
template: "<div><button ng-click='ctrlFn()'>Click Here</button></div>",
link: function(scope, element, attributes) {
scope.ctrlFn(count);
}
};
});
Here is the template code I'm trying to modify in the main code base:
<div>
<div layout="row">
<results-loader ctrl-fn="ctrlFn(count = count + 10)"></results-loader>
<md-button class="md-raised md-primary md-button" ng-click="ctrlFn()" flex>Click Me</md-button>
</div>
</div>
and here is where I use an existing controller in my directive as a parent controller. It's defined in a route, rather than ng-controller and is already used for this view.
myresultsCtrl.$inject = ['$scope', ' myService'];
/* #ngInject */
function myresultsCtrl($scope, myService) {
$scope.count = 0;
etc...
however, it apparently isn't bound properly as I never hit the directive or this function with ng-click.
Like I said I tried adding a new controller to the template, then I tried injecting an existing controller into the directive, but neither worked. I took out the template from the directive and tried to put the ng-click directly into the template with ctrl-fn, but I wasn't sure how to wire up the click with the call to the ctrl-fn attribute of the directive with both in the template? The idea here is to move the template into it's own html file and reference it from the directive, as in: template: "myFile.html. I'm trying to enscapsulate as much as possible to make this into a reusable component.
I haven't worked much with custom directives.
Here is the direct link to the plunker.
I don't know your exact assignment, but rethink your architecture. Why do you want to count the value in the directive? In your simple case it would be better to count the value in a service or in the controller, not a directive.
Injecting controller in the directive is anti-angular pattern. You need to rethink your intentions. It is better to pass the number to the directive, make your calculations there and send your number back to the controller. It would work without extra code, because of the two-way data binding. Here a fork of your plunker:
http://plnkr.co/edit/KpB6bv5tHvXSvhErLcEp?p=preview
Main part is the definiton of the directive:
<div><span>{{count}}</span><br /><button ng-click='myFunction()'>Calculate</button></div>
I prefer not to answer my own questions as that can have a negative appearance, but in this case, I don't know if I could have explained the question well enough to get the right answer. What was throwing me off was integrating my working plunker code into our existing code base.
Where I got stuck was how to setup the controller properly for this use case. Though I declared my controller, in the module and injected the controller as it was already bound to that view, I needed to define the controller in the directive itself. Once I did all three together, everything started working.
Here are some code snippets:
angular.module('directiveName', [])
.directive('directiveName', directiveName)
.controller('injectedCtrl', injectedCtrl)
etc...
var directive = {
restrict: 'E',
scope: {
'ctrlFn' : '&'
},
template: "<div><button ng-click='ctrlFn()'>Click Here</button></div>",
controller: "injectedCtrl",
link: function(scope, element, attributes) {
scope.ctrlFn(); //This will pass to the parent controller: injectedCtrl, where $scope resides.
}
}
return directive;
}
injectedCtrl.$inject = ['$scope', 'myService'];
/* #ngInject */
function injectedCtrl($scope, myService) {
$scope.ctrlFn = function() {
//do something
}
etc...
HTML CODE:
When the button is clicked from the directive, ctrlFn here is a reference that is under my directives isolated scope in the DDO. This takes the external function that was passed in, namely: ctrlFn() which can then be invoked from the directive to call the parent controller.
<div layout="row">
<results-loader ctrlFn="ctrlFn()"></results-loader>
</div>
I'm posting this to help someone else that might have this use case as it was not easy to figure this out.
Dah Wahlins post goes into this subject in greater detail: Creating Custom AngularJS Directives Part 3 - Isolate Scope and Function Parameters and what helped to get my thinking straightened out.

how to change the angularjs custom directive templateUrl dynamically with using scope

This is my first question so I hope I can explain the situation
The angularJs documentation here talks about having the directive templateUrl as a function to be returned dynamically. There is also a Plunkler live demo here.
.directive('....', function() {
return {
templateUrl: function(elem, attr){
return **.... scope.Somthing ...**;
}
};
});
the function does not take a scope parameter and this is the main issue
The only way so far that i found is to set the TemplateUrl dynamically with relevance to the directive scope is this way
.directive('....', function() {
return {
link: function (scope, element, attrs) {
scope.getTemplateUrl = function () {
return **.... scope.Somthing ...**;
};
},
template: '<ng-include src="getTemplateUrl()"/>'
};
});
another solution is
.directive('....', function() {
return {
controller: function ($scope) {
$scope.getTemplateUrl = function () {
return **.... scope.Somthing ...**;
};
},
template: '<ng-include src="getTemplateUrl()"/>'
};
});
my first issue is that this looks like patch to the problem
my second issue is having to build html string in the directive.
Is there any other way to achive ?
Lets split your problem for 2 problems :D
First problem is about storing template in js.
This problem can be solved using the $templateCache and injecting
<script id="myTemplate.html" type="text/ng-template"> <div> SOME MARKUP <div> </script>
Here you can read more about this
Second problem is about dynamic templating.
So there is 2 solutions (as far as i know :D)
First solution you have already mentioned - using ng-include.
Second solution is to use $compile to dynamicly compile html with angular directives.
First solution is little bit better, because in the second case you have to always remember about memory leak. Look here for more info and here

Angular script won't run when first loaded

On page load the console log prints but the toggleClass/click won't work I even use angular.element but it has the same result.I need to change state in order for the toggleClass to work.I dunno what's wrong in my code.
.run(['$rootScope', function ($rootScope) {
console.log('test');//this prints test and it's ok
//this part won't load at the first loading of page.
$('.toggle-mobile').click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
//....
}])
even doing it this way doesn't work.
$rootScope.$on('$viewContentLoaded', function () {
angular.element('.toggle-mobile').on('click', function (event) {
angular.element(this).toggleClass('toggle-click');
angular.element('.menu-mobile').toggle();
event.preventDefault();
});
});
The Angular way to render items is different from "On DOM Ready" that is why we need to treat these as 2 separate things.
Angular could render items later on even after DOM is ready, this could happen for example if there is an AJAX call($http.get) and that is why a directive may be the recommended approach.
Try something like this:
<body ng-controller="MainCtrl">
<div toggle-Me="" class="toggle-mobile"> Sample <div class="menu-mobile">Sample 2</div>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {}]);
myApp.directive("toggleMe", function() {
return {
restrict: "A", //A - means attribute
link: function(scope, element, attrs, ngModelCtrl) {
$(element).click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
}
};
});
...
By declaring the directive myApp.directive("toggleMe",... as an attribute toggle-Me="" every time angular generates the input element it will execute the link function in the directive.
Disclaimer: Since the post lacks from a sample html I made up something to give an idea how to implement the solution but of course the suggested html is not part of the solution.

Resources