Angular expression selecting a directive - javascript

I need to render a angular directive, selecting it by appealing to a string previously defined at a variable (usually declared in the controller). Despite such a variable is accessible as an Angular expression, when I try to use for selecting a directive it doesn't work:
<!DOCTYPE html>
<html ng-app="app">
<body ng-controller="TextController">
<!-- item.dir is accessible: -->
<div>Value of item: {{item.dir}}</div>
<!-- It works. the directive "hello" is rendered -->
<div class="hello"></div>
<hello></hello>
Here you should see additional text:
<!-- Doesn't work item.dir is not recognized-->
<!-- as a class -->
<div class="{{item.dir}}"></div>
<!-- as an attribute-->
<div {{item.dir}}></div>
<!-- trying ng-class (it fails)-->
<div ng-class="item.dir"></div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular.min.js"></script>
<script>
var appModule = angular.module('app', []);
// The directive to render
appModule.directive('hello', function() {
return {
restrict: 'ACE',
template: '<div>works: Transcoded text</div>',
replace: true
};
});
appModule.controller('TextController', function ($scope) {
$scope.item = {dir: 'hello'}; // the name of the directive, the idea is to use it for referring to many future directives.
});
</script>
</body>
</html>
Here's a plunker of the code: http://plnkr.co/edit/tM73sY3vTfPFHmn6iCYE?p=preview
So, what I am missing? How do I achieve to get Angular using string interpolation when using a directive? Thanks!

For directives to work, Angular needs to compile your html (something automatically done when a page is loaded).
Having a way to control freely which directive to instantiate is a bit like pulling the rug under your feet and is atypical. One of the issue is that the compilation "destroy" the internal binding/watchers data and some of the original DOM and thus there is not enough information to "recompile" a DOM node.
NOTE: you cannot change attribute or element names (only attribute values) with angular using this type of binding: {{ }} But ng-class="..." and class="{{...}}" works.
I do not understand what you are trying to achieve exactly. If the intention is really about modifying the item.dir's value and having Angular "reconfigure" your application, it is "possible" but I highly suspect it will induce "state" drawbacks.
Nevertheless, here's a working "hack" that "remembers" the original DOM html and recompiles it when needed. This is done in 2 compilation phases: First phase is to restore the original bindings and a second phase that runs after the $digest cycle so the original bindings finished populating the class name from the scope (i.e. to have item.dir in effect). The drawback of course is if you made modifications to the enclosing DOM, this will wipe them! Alternatively, it might be possible to remember only specific attributes and revert "that" only while keeping other portion of the DOM intact (but might create other issues).
appModule.directive('forceRecompilation', ['$timeout', '$compile', function($timeout, $compile) {
return {
restrict: 'A',
link: function(scope, element, attr) {
var originalHtml = element.html();
scope.$watch(attr.forceRecompilation, function(){
// restore original HTML and compile that
element.html(originalHtml);
$compile(element.contents())(scope);
// wait for all digest cycles to be finished to allow for "binding" to occur
$timeout(function(){
// recompile with bounded values
$compile(element.contents())(scope);
});
});
}
};
}]);
...to be used as an enclosing tag of the section of the DOM to be acted upon. It will "revert & recompile" everything under it when the expression changes. (here "item.dir"):
<div force-recompilation="item.dir">
<div class="{{item.dir}}">
</div>
Plunker: http://plnkr.co/edit/TcMhzFpErncbHSG6GgZp?p=preview
In the plunker, there are 2 directives "hello" and "hello2". Change the text to "hello" and back to "hello2" to see the effect.
EDIT: The following is a directive that allows inserting markup to be compiled as described in a comment below. This is just a slightly modified version of Angularjs - inline directives with ng-bind-html-unsafe
angular.module('bindHtmlExample', ['ngSanitize'])
.controller('ExampleController', ['$scope',
function($scope) {
$scope.test = false;
$scope.dir = "ng-click";
$scope.clicked = function() {
$scope.test = !$scope.test
}
$scope.myHTML =
'I am an <b ng-show="test">invisible</b> HTML string with ' +
'links! and other <em>stuff</em>';
}
])
// modified plunker taken from https://stackoverflow.com/questions/18063280/angularjs-inline-directives-with-ng-bind-html-unsafe
//
// Allows an attribute's value to be evaluated and compiled against the scope, resulting
// in an angularized template being injected in its place.
//
// Note: This directive is prefixed with "unsafe" because it does not sanitize the HTML. It is up
// to the developer to ensure that the HTML is safe to insert into the DOM.
//
// Usage:
// HTML: <div unsafe-bind-html="templateHtml"></div>
// JS: $scope.templateHtml = '<a ng-onclick="doSomething()">Click me!</a>';
// Result: DIV will contain an anchor that will call $scope.doSomething() when clicked.
.directive('unsafeBindHtml', ['$compile',
function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.unsafeBindHtml);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM element
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);
}
);
};
}
]);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular-sanitize.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="bindHtmlExample">
<div ng-controller="ExampleController">
<p unsafe-bind-html="myHTML"></p>
(click on the link to see <code>ng-click</code> in action)
</div>
</body>
</html>

Apparently, the tg-dynamic-directive does the trick.

Related

angularjs click event, after appending content [duplicate]

I am working with angularjs 1.2.0-rc.3. I'd like to include html code into a template dynamically. For that I use in the controller :
html = "<div>hello</div>";
$scope.unicTabContent = $sce.trustAsHtml(html);
In the template I got :
<div id="unicTab" ng-bind-html="unicTabContent"></div>
It works fine for regular html code. But when I try to put angular template it is not interpreted, it is just included in the page. For example I'd like to include :
<div ng-controller="formCtrl">
<div ng-repeat="item in content" ng-init="init()">
</div>
</div>
Thanks a lot
This solution doesn't use hardcoded templates, and you can compile Angular expressions embedded within an API response.
Step 1.
Install this directive: https://github.com/incuna/angular-bind-html-compile
Step 2. Include the directive in the module.
angular.module("app", ["angular-bind-html-compile"])
Step 3. Use the directive in the template:
<div bind-html-compile="letterTemplate.content"></div>
Result:
Controller Object
$scope.letter = { user: { name: "John"}}
JSON Response
{ "letterTemplate":[
{ content: "<span>Dear {{letter.user.name}},</span>" }
]}
HTML Output =
<div bind-html-compile="letterTemplate.content">
<span>Dear John,</span>
</div>
For reference sake, here's the relevant directive:
(function () {
'use strict';
var module = angular.module('angular-bind-html-compile', []);
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 what I've made, no idea if it's the angular wayTM, but it works and is super simple;
.directive('dynamic', function($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(attrs.dynamic, function(html) {
$compile(element.html(html).contents())(scope);
});
}
};
});
So;
<div id="unicTab" dynamic="unicTabContent"></div>
Edit: I found the angular way, and it's super simple.
$templateCache.put('unicTabContent', $sce.trustAsHtml(html));
<div id="unicTab" ng-include="'unicTabContent'"></div>
Don't need to make your own directives or anything.
But it's a bind-once sort of deal, it wont see changes made to your html like the custom directive does.
As Vinod Louis says in his comment, the best way to do that was to use templates. I had to define a template outside of the regular code, for example I added that code inside of my index.html :
<script type="text/ng-template" id="unic_tab_template.html">
<div ng-switch on="page">
<div ng-switch-when="home"><p>{{home}}</p></div>
<div ng-switch-when="form">
<div ng-controller="formCtrl">
<div ng-repeat="item in content">{{item.name}}:{{item.value}}</div>
</div>
</div>
<div ng-switch-default>an error accured</div>
</div>
</script>
This template is conditional, so depending on the value of $scope.page, it switches between the 3 templates (the third being an error handler). To use it I had :
<div id="unicTab" ng-controller="unicTabCtrl">
<div ng-include="'unic_tab_template.html'"></div>
</div>
That way my page changes depending on the $scope inside of my unicTabCtrl controller.
To conclude the idea of inserting angularsjs template seams to be difficult to realize ($compile seams to be the solution, but I wasn't able to make it work). But instead you may use conditional templating.
I was trying to do the same thing and came across this module.
http://ngmodules.org/modules/ng-html-compile
I just included it and then I was able to use "ng-html-compile" instead of "ng-bind-html"
My solution to similar problem in my current app without using template (not elegant, but working):
directive('ngBindHtmlCompile', ['$compile', function ($compile) {
return {
restrict: 'A',
compile: function compile(tElement, tAttributes, transcludeFn) {
return function postLink(scope, element, attributes) {
scope.$watch(function() {
return scope.$eval(attributes.ngBindHtml);
}, function(newValue, oldValue) {
$compile(element.children())(scope);
});
};
}
};
}]);
It requires ngBindHtml on the same element and compiles the element content after it changes with ngBindHtml.
<div id="unicTab" ng-bind-html="unicTabContent" ng-bind-html-compile></div>
ng-html-compile looks similar but at first glance it won't be recalculated when the template content is changing. But I haven't tried it.
One way is use a directive for purpose of inserting custom templates that include angular expresssions
<div id="unicTab" unic-tab-content></div>
app.directive("unicTabContent",function(){
return {
restrict:"A",
template:'{{unicTabContent}}'
}
})
The code below is much simpler using Angular's built-in $interpolate and $sce objects. First inject the $interpolate and $sce Angular objects into your directive as you do anything custom you need in your directive.
amqApp.directive('myDir', ['$interpolate', '$sce', function ($interpolate,$sce ) {...}
Then create all your scoped variables found in your imported html expressions...
$scope.custom = 'Hello World';
Next use $interpolate to process your custom HTML and its expressions...then make sure you use the $sce object to trust it as HTML before binding...
var html = $interpolate('<b>{{custom}}</b>')($scope);
$scope.data = $sce.trustAsHtml(html);
Finally, in your view, just make sure use an element with the "ng-bind" or "ng-bind-html" on it in your view display. I found the $sce piece wont display the HTML as HTML (sees it as text) if you don't bind it in your html template like this...
<span ng-bind-html="data"></span>
You should see in bold...
Hello World
I used this trick to import in text/HTML with custom angular {{expressions}} from a web.config.
A lot simplified solution of #clement-roblot based on built-in templates.
Controller:
app.controller('TestCtrl', [
'$scope',
'$templateCache',
function ($scope, $templateCache) {
$templateCache.put('test.html', '2 + 2 = {{ 2 + 2 }}');
}
]);
View:
<div ng-include="'test.html'"></div>

Passing Attribute Value to Directive Template

End Goal
We'd like to have an attribute we can add to any element which we would associate to a contextual help dialog so that when we hover the mouse cursor over an element, a pop-up will show the help information. Here's an example of what we want to do:
<label help-info="This is what will be displayed in the help dialog">Help Example</label>
I'm having some trouble correctly passing the string parameter to the template though.
Here's what I've tried:
Index.html
<html ng-app="myApp">
<head>
<script data-require="angular.js#*" data-semver="2.0.0-alpha.20" src="https://code.angularjs.org/2.0.0-alpha.20/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="HelpInfoDirective.js"></script>
</head>
<body ng-controller="myCtrl">
<div help-info="test"></div>
</body>
</html>
HelpInfoTemplate.html
<div>
{{'this is a' + _attrs.help-info}}
</div>
HelpInfoDirective.js
(function(){
var app = angular.module('myApp', []);
app.directive('helpInfo', function(){
return{
restrict: 'A',
scope: true,
templateUrl: function(_elements, _attrs){
'HelpInfoTemplate.html'
}
// link: function(_scope, _elements, _attrs){
// _scope.helpAttr = _attrs.help-info;
// }
}
});
})();
First, I tried passing the parameter by using the link parameter and it didn't work so I tried placing the template into a function with no luck.
What is the correct way to pass an attribute's value to a template?
Also, once we have the value, how can we modify the returned template so we can use ng-show to show the pop-up (we'll place it into a modal eventually). I'm open to suggestions.
Link to the Plunk
What is the correct way to pass an attribute's value to a template?
Binding values to templates
Values are bound to templates through the compilation process. You can compile code in the link function of your directive using the $compileservice and providing the scope that the template needs to bind to:
app.directive('helpInfo', function($compile){
return{
restrict: 'A',
scope: {title : '#'},
link: function(scope, el){
var newElement = $compile('<div>{{title}}</div>')(scope);
el.hover(function(){
el.after(newElement);
});
el.mouseout(function(){
el.nextAll().first().remove();
});
}
}
});
Defining the scope for your directive
You'll need to setup an isolated scope for the directive an specify the name of the attribute where you'll define the text shown in your popup. I've used 'title' as the attribute in this example.
You can then use the directive like this:
<div help-info title="I am a title">Click me</div>
Demo
Here's a Plunker that shows this in action.
http://plnkr.co/edit/VwdHC3l9b3qJ4PF6cGV1?p=preview
Also, once we have the value, how can we modify the returned template so we can use ng-show to show the pop-up
In the example I provided I used the jQuery hover() and mouseout() events to respond to users hovering over and away from the DOM element. If you want to take this further, here's a tutorial that shows how to put popups or alerts into services.
You need to assign it like this:
app.directive('helpInfo', function(){
return{
restrict: 'A',
scope: true,
template: '<div>{{helpAttr}}</div>',
link: function(_scope, _elements, _attrs){
_scope.helpAttr = _attrs.helpInfo;
}
}
});
Demo
You can just use your own template and insert {{helpAttr}} binding wherever you need it.

querySelectorAll in AngularJS partial ng-view

I'm trying to use queryselectorAll to get all elements with a certain class name. I have a problem where I can only get results from elements in the index.html page. If I try to get a nodelist from a partial (ng-view) html page I get an empty result back.
App.js
var myApp = angular.module('myApp', ['ngRoute', 'articleControllers']);
myApp.config(['$routeProvider', function($routeProvider){
$routeProvider.
when('/main', {
templateUrl: 'lib/partials/main.html',
controller: 'MainController'
})
}]);
controller.js
var articleControllers = angular.module('articleControllers', []);
articleControllers.controller('MainController', ['$scope', '$http', function ($scope, $http){
$http.get('http://...').success(function(data) {
$scope.articles = JSON.parse(data);
});
}]);
index.html
(body, header, ...)
<section ng-view>
</section>
(footer, ...)
lib/partials/main.html
...
<div class="nextArticle">
<button class="next_btn" title="View next article"> link text</button>
</div>
...
finally: helper.js (which is a script I call like every other script in index.html)
var elList = document.querySelectorAll('.next_btn');
Array.prototype.forEach.call(elList, function(el) {
console.log("Found: " + el);
});
So to recap:
Its like querySelectorAll can only find elements in the index.html and not in the partial views with ng-view.
Anyone an idea of what could be wrong?
I'm trying not to use jquery.
Move your helper.js code to custom directive. https://docs.angularjs.org/guide/directive
What are Directives?
At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children.
That is because ng-view load the template after angular gets loaded, and the JS code which you have added is firing before the ng-view renders the template, I think you could solve this by writing a directive for ng-view that will will fire your jQuery code once your ng-view content gets loaded
Basically you need to wrap your code in element.on('load' event so that it will ensure that code will available when jQuery code is firing
app.directive('ngView', ['$timeout', function($timeout){
return {
restrict: 'AE',
link: function(element, attrs){
$timeout(function(){
element.on('load', function(event){
//your jQuery code will lie here
//that will also get the DOM of ng-view template
});
});
}
}
}]);

AngularJS, directive element is null

I'm working on porting a jQuery plugin to AngularJS just because it seems fun.
In the past, when using jQuery, I was using jQuery to manipulate the DOM. So, I have a function in jQuery to load the plugin and in that function is was maniuplating the DOM.
Now, when using AngularJS, I've read that there are directives for that specific purpose, but I don't manage to find the solution.
I have the following html:
<body class="officeui-space-no-margin officeui-space-no-padding">
<!-- Defines the OfficeUI section. In this section, all the contents for the OfficeUI user interface will be written. -->
<div ng-controller="OfficeUIController" id="OfficeUI">
<div class="title officeui-align-center">
<span>Here the title can go.</span>
</div>
<!-- Defines the main holder for the ribbon. -->
<div id="ribbonHolder">
<!-- Render the template for the ribbon. -->
<ng-include src="'Partials/Templates/Ribbon/tabs.html'"></ng-include>
</div>
</div>
<!-- Bottom scripts: Used for Initialization. -->
<script type="text/javascript">
// Initialize the 'ribbonHolder' element as a ribbon.
$('#ribbonHolder').ribbon();
</script>
</body>
You see here that I'm loading a template to render, of which the contents can be found below:
<ul role="tablist" class="officeui-space-no-margin officeui-space-no-padding">
<!-- Render all the tabs in the collection. -->
<tabs-container>
<li role="tab" class="officeui-display-inline-block officeui-align-center" ng-repeat="tab in tabs">{{tab.Name|tabs}}</li>
</tabs-container>
</ul>
In the template above, I do have an element tabs-container which should be my directive.
I've the JavaScript that defines the AngularJS stuff, including registering this directive:
var officeUIApplication = angular.module('OfficeUI.Ribbon.Controllers', []);
officeUIApplication.directive('tabsContainer', function() {
var functionLink = function(scope, element, attrs) {
console.log(element.children());
};
return {
restrict: 'E',
link: functionLink
};
});
According to my very limited knowledge of AngularJS, I'm just learning it, it's in the variable functionLink where I should manipulate the DOM of attach event handlers to specific parts.
But inside functionLink I call the following code:
console.log(element.children());
But in the console I do see that this particular element is empty.
Why is that, and is this the good approach?
Another approach of which I've tought it to include something in jQuery so that the code is only executed after AngularJS has finished it's work, but I don't like that particular idea, on the other hand, how do I create a jQuery plugin that that passes options and event handlers, or isn't that possible anymore and am I in fact creating an AngularJS plugin?
Thanks for the response.
Try this:
officeUIApplication.directive('tabsContainer', function($timeout) {
var functionLink = function(scope, element, attrs) {
$timeout(function() {
element.ribbon();
});
};
return {
restrict: 'E',
link: functionLink
};
});
jQuery plugins usually require the DOM to be ready for it to initialize properly. In angular, there is no real concept of DOM ready. $timeout is executed after the render phase, which is why it works.

Template inside of a directive

I have a strange situation where I need to put a template inside of a template in my directive. The reason for this is that AngularJS will not read ng-repeat code inside of attributes.
In an ideal world, this is what my code would look like:
<div ng-repeat="phone in phones">
<button popover="<div ng-repeat='friend in phone.friends'>{{friend.name}}</div>"></button>
</div>
Unfortunately this does not work because of the quotes around the popover attribute. This has led me down a pretty deep rabbit hole where I'm trying to put a template inside of a template like in this plunker:
http://plnkr.co/edit/ZA1uA1UOlU3cCH2mbE0X?p=preview
HTML:
<div my-popover></div>
Javascript:
angularApp.directive('myPopover', function( $compile) {
var getTemplate = function()
{
var scope = {title: "other title"};
var template = "<div> test template. title: {{title}}</div> ";
return $compile(template)(scope);
}
return {
restrict: 'A',
template: '<button class="btn btn-default" popover="{{content}}" popover-title="title">template</button>',
link: function(scope) {
scope.content = getTemplate();
}
};
})
Unfortunately this does not work because AngularJs complains about a circular reference. Please help! (this has been taking me all day)
I'm not sure I understand exactly what you are trying to achieve, but from the look of it you might want check out the transclude option for directives.
From the docs:
use transclude: true when you want to create a directive that wraps
arbitrary content.
If you use transclude, you can store the popover content inside the button, and "forward" that content to where you want it using the ng-transclude directive.
Your code would then look something like this:
<button>
<div ng-repeat='friend in phone.friends'>{{friend.name}}</div>
</button>
You can see some examples in action in the guide to directives.

Categories

Resources