Angularjs directive removes all content from parent div - javascript

I have custom directive declaration with replace set to true.
When I place it in html with separate close tag:
<div>
<search-box search="search(query)" query="query" ></search-box>
<div class="dataDiv">
<!--other div elements -->
</div>
</div>
everything works as expected.
But after rewriting html in this way:
<div>
<search-box search="search(query)" query="query" />
<div class="dataDiv">
<!--other div elements -->
</div>
</div>
directive completely replaces all parent div content with it's template and removes dataDiv from resulting html page.
Is this expected angular behavior or something that can be changed in directive declaration?
Directive:
function SearchBox() {
return {
restrict: 'E',
replace: true,
template: '...',
scope: {
query: '='
},
link: function($scope, $element){
...
},
controller: function ($scope) {
...
}
}
}

Depending on your doctype you might be giving the wrong meaning to the "closing" slash.
by default, yeoman generator and angular-seed use <!doctype html> (html5)
In HTML 5, <foo /> means <foo>. The slash is just syntactic sugar
Check this Are (non-void) self-closing tags valid in HTML5?
Depending on the browser implementation, it will add automatically a closing tag in one place or another, just like when you leave bodyor strong open and read the rendered code.

Related

In a named transclude slot, how do I transclude *only* the element's contents?

I've got a basic directive that I want to use transclusion. The relevant options are set up as follows:
restrict: "E",
replace: true,
transclude: {
'toolbar': 'toolbarSlot'
},
scope: {
pageTitle: "#"
},
templateUrl: "path/to/my/template.html"
My template is:
<header class="page-header">
<h1 class="page-title heading">{{ pageTitle }}</h1>
<div class="page-header-toolbar toolbar" ng-transclude="toolbar">
<!-- "toolbar" transcluded content should go here -->
</div>
</header>
And finally, when I use the directive, I'm using it this way:
<my-custom-page-header-directive page-title="Title">
<toolbar-slot>
<button>Button</button>
<button>Another button</button>
</toolbar-slot>
</my-custom-page-header-directive>
The problem is, in the DOM it ends up as something like this, with an extraneous toolbar-slot element mixed into the transcluded content:
<header class="page-header">
<h1 class="page-title heading">Title</h1>
<div class="page-header-toolbar toolbar">
<toolbar-slot>
<button>Button</button>
<button>Another button</button>
</toolbar-slot>
</div>
</header>
Is there a way (using ngTransclude) to only transclude the contents of the slot element?
Looks like the answer is no, there isn't a way to do that as of right now.
An open issue on the Angular.js repository shows that there are misgivings about implementing the ability to use ngTransclude that way:
I'm not a fan of adding a replace functionality to the slot transclusion. It would introduced the same problems we have with normal "replace" and transclude: element.
But, apparently, you "can actually do this manually if your really need it".

Transclude example not working for me

I've been taking a look for this tutorial, and now I'm trying to follow it. But somehow, when I reach the following JSBin and paste it all on my test folder, it just won't work:
http://teropa.info/blog/2015/06/09/transclusion.html
You can see at the right side the card showing up perfectly. Well, when I copy paste this code, the content doesn't get rendered inside the "content" div of the template, which means that transclusion isn't working at all.
What may be happening? The code is perfectly pasted, both HTML, CSS and JS. Even tried with my local version of Angular (last one).
But the content keeps being hidden! Any help with this? I really wanna learn how the transclusion works.
Consider I have created a directive called myDirective as an element
<div ng-controller="myCtrl">
<my-directive>
<button>some button</button>
and a link
</my-directive>
</div>
myDirective has a template which is using transclude
myApp.directive('myDirective', function(){
return{
restrict: 'E',
transclude: true,
template: '<div class="something" ng-transclude> my directive goes here...</div>'
}
});
It will render the DOM as
<div class="something">
my directive goes here...
<button>some button</button>
and a link
</div>.

Why does angular differentiate between `<my-element></my-element>` and `<my-element />`?

I just discovered a weird issue with Angular 1.2.1, demonstrated in this fiddle (tested in both IE, FF, and Chrome): if I create a very simple templated directive, it fails to work as <my-element /> if it hasn't first been used as <my-element></my-element>.
This is the js code from the fiddle in its entirety:
angular.module('app', []);
angular.module('app').directive('myElement', function() {
return {
restrict: 'E',
replace: true,
template: '<p>Hello, element!</p>'
};
});
and the html:
<div ng-app="app">
<my-element />
<my-element></my-element>
</div>
The output I expect from that is two paragraphs with the contents Hello, element!, but I only get one. If, however, I reverse the order of the two <my-element>-tags, so that the self-closing one comes after, both of them give output.
Why does Angular behave in this pathological way?
Your own self-closed tag should not be used
The browser interprets if a tag is "self-closeable" or not.
It doesn't know your tag as "self-closeable" (he knows only the HTML tags) and this is why it has a strange behavior.
On www.w3.org you can find the Elements section:
A void element is an element whose content model never allows it to have contents under any circumstances.
The following is a complete list of the void elements in HTML:
area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
A non-void element must have an end tag, unless the subsection for that element in the HTML elements section of this reference indicates that its end tag can be omitted....
So, the conclusion is: don't use the short form for your own tag.
Interpreting your own self-closed tag in browser
That strange behavior appears because the browser interprets a self-closed tag like a parent of all siblings that come after it.
Let's ignore the AngularJS for the moment, and let's consider 3 new tags that are not in HTML: <x>, <y> and <z>.
E.g.:
<x></x>
<y></y>
<z></z>
will appear in the source code (F12 in Chrome) as they are.
But
<x></x>
<y/>
<z></z>
will look like:
<x></x>
<y>
<z></z>
</y>
Now, they we have this information, let's come back to AngularJS:
<my-element />
<my-element></my-element>
becomes
<my-element>
<my-element></my-element>
</my-element>
and the "main" = parent <my-element> is found and replaced by <p>Hello, element!</p>. => one output line => in this case you can also have some text or other tags after the 2nd line of code, because they will be wrapped in the parent and the result will be the same.
But, on the other hand:
<my-element></my-element>
<my-element />
will become
<my-element></my-element>
<my-element></my-element>
that will be displayed 2 times.
Here is the issue :-
https://github.com/angular/angular.js/issues/1237
In HTML there is no such thing as a self closing tag. There are only tags which are leafs. Leaf tags are fixed.
As an addition to earlier answers, this will give a better idea on what's happening. Run this in jsfiddle.
The self closing tag is assumed to be the starting tag and all the text following is thought of as directive's content.
angular.module('app', []);
angular.module('app')
.directive('myElement', function () {
return {
restrict: 'E',
transclude: true,
scope: {},
template: '<p>Hello, <span ng-transclude></span> element!</p>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<div ng-app="app">
<my-element/>
<my-element>jfdsfkl</my-element>
<my-element>jfdsfkl</my-element>
<my-element/>
<my-element>jfdsfkl</my-element>
</div>

How to dynamically nest directives in AngularJS

New to Angular and need some assistance.
I have a block of HTML content that will be coming from a database that will contain a group of widgets. These are simple widgets that will essentially render out various elements, but for the purposes of this question we'll assume they're all basic HTML inside.
Those widgets are included in an unpredictable way, so my first thought was to use directives to render the HTML. So, we'd have something like:
<div widget data="This is the content."></div>
So I've got a directive that will place the value of data into the div. Easy enough!
Now, how would I go about nesting those widgets? So, how would I get something like:
<div widget data="Welcome! ">
<div widget data="This is some inside content."></div>
</div>
to render out:
Welcome! This is some inside content.
... because the issue I'm noticing is that if I place anything inside the directive HTML, it essentially gets ignored since it gets replaced with its own result (thus only echoing out Welcome!).
I realize I may be going the wrong direction on this in the first place, so any insight would be greatly appreciated. Thanks so much!
This is where you need to use the transclusion feature of the directive combined with ng-transclude directive.
Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
A very basic version of transclusion of content based on your example might look something like this:
.directive('widget', function() {
return {
transclude: true,//Set transclusion
template: '{{text}} <section ng-transclude></section>', <!-- set where you need to present the transcluded content -->
scope: {
text: "#"
}
}
});
Demo
angular.module('app', []).directive('widget', function() {
return {
transclude: true,
template: '{{text}} <section ng-transclude></section>',
scope: {
text: "#"
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<span widget data-text="Welcome! ">
<div widget data-text="This is some inside content.">
<span widget data-text="This is some inside inside content."></span>
</div>
</span>
</div>

Angular expression selecting a directive

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.

Categories

Resources