Angular directive which isn't present in the DOM - angularjs

I would like to have a directive that I can use for grouping ng-if or ng-switch-when like:
<empty ng-switch-when="expression">
... Lots of little DOM nodes ...
</empty>
If I just do
.directive('empty', function() {
return {
restrict: 'E',
};
})
then it works, but still has an <empty> in the DOM, to which CSS selectors are applying (particularly problematic are the direct child selectors). So can I get it out of the DOM completely?
For example: http://plnkr.co/edit/FBFHS7A66he73xA2wlNy?p=preview should have a red link.

Try this:
app.directive('empty', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
link: function (scope, element, attrs, ctrl, transclude) {
transclude(scope, function (content) {
element.replaceWith(content);
});
}
};
})
Or you can likewise use scope.$parent (depending on which scope you need), e.g.
transclude(scope.$parent, function (content) {
Working plunker here.

Try this
app.directive('empty', function() {
return {
restrict: 'E',
link: function (scope, element, attrs) {
element.replaceWith($compile(element.contents())(scope));
}
};
})
Demo http://plnkr.co/edit/2cbpiWOIW5EUuuDjkbB5?p=preview

How about this:
app.directive('empty', function ($compile) {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var content = $compile(element.html())(scope);
element.parent().prepend(content);
element.remove();
}
};
})
http://plnkr.co/edit/ttqO91Dcr4Y9INuyJUlO?p=preview
Note: if you want to make sure the element is exactly at the same place, you can use jQuery's insertBefore instead of parent().prepend

For ng-if you can use ng-if-start and ng-if-end, much like ng-repeat-start and ng-repeat-end:
<p ng-if-start="true">First visible paragraph</p>
<p>Another visible paragraph</p>
<p ng-if-end>Last visible paragraph</p>
<p ng-if-start="false">First hidden paragraph</p>
<p>Another hidden paragraph</p>
<p ng-if-end>Last hidden paragraph</p>
which can be seen working at http://plnkr.co/edit/c7C0WDx93rMSo3i9Taro?p=preview
For ng-switch you can use ng-switch-when-start and ng-switch-when-end:
<div ng-switch on="selection">
<div ng-switch-when-start="a">A 1</div>
<div>A 2</div>
<div ng-switch-when-end>A 3</div>
<div ng-switch-when-start="b">B 1</div>
<div>B 2</div>
<div ng-switch-when-end>B 3</div>
</div>
which can be seen working at http://plnkr.co/edit/cRjHcmjODBzwm1BjeSuO?p=preview
To achieve this, I suspect ngIf and ngSwitchWhen must use the multiElement option when they're defined.

Is there some reason that you cant make it a comment directive?
As in restrict: 'M'
and then:
<!-- directive: empty -->
https://code.angularjs.org/1.3.16/docs/guide/directive
scroll down to directive types for more info

Related

Pass argument between parent and child directives

I have parent directive for navigation menu and child directives for menu links. Something like this:
<menu>
<menu-link />
<menu-link />
</menu>
In menu directive I use ng-translucent to be able to add html.
Is there any way to pass argument from menu to all menu-link elements?
For example, if:
<menu ng-disabled='true'..
I want all menu-link directives to get that value from parent.
One more thing: each menu-link have its own attributes, so it needs to have own scope.
You can make use of require, for more info read the angular directive doc.
Refer the example for more info:
angular.module('myApp', [])
.controller('MyController', MyController)
.controller('MyDirectiveController', MyDirectiveController)
.directive('myDirective', myDirective)
.directive('childDirective', childDirective)
function MyController($scope) {
}
function MyDirectiveController($scope) {
this.isDisabled = function() {
return $scope.disabled;
};
}
function myDirective() {
return {
restrict: 'E',
transclude: true,
template: '<div>myDirective Disabled: {{ disabled }}<ng-transclude></ng-transclude></div>',
scope: {
disabled: '=?ngDisabled'
},
controller: 'MyDirectiveController'
};
}
function childDirective() {
return {
restrict: 'E',
require: '^^myDirective',
template: '<div>childDirective disabled: {{ disabled }}</div>',
scope: {},
link: function(scope, elem, attrs, myDirectiveCtrl) {
scope.disabled = myDirectiveCtrl.isDisabled();
}
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyController">
<my-directive ng-disabled="true">
<child-directive></child-directive>
<child-directive></child-directive>
</my-directive>
</div>
</div>

AngularJs transclude not working in Directive template or templateURL

I have written a custom directive like so, notice I have commented out the template URL that contains the same HTML structure and the template property:
.directive('sillyDirective', function ( ) {
'use strict';
return {
restrict: 'A',
replace: false,
transclude: true,
template: '<h2>Welcome to my site</h2>',
//templateUrl: '/views/hello.html' ,
link: function (scope, element, attrs) {
element.bind('click', function (){
alert('you click me! I am clicked');
});
};
});
In my HTML view I have the following...
<div data-silly-directive>
<div><img src="logo.jpg></div>
<div><h1>My First Website</h1></div>
</div>
The problem is the content of the directive, e.g.:
<div><img src="logo.jpg></div>
<div><h1>My First Website</h1></div>
is being overwritten with the template content even thought I have set transclude to true and replace to false? What am I doing wrong here?
you need to specify ng-transclude in the template of your directive, this will let angular know where to insert the content of the markup.
app.directive("foo", function() {
return {
transclude: true,
template: "<div>the template</div><div ng-transclude></div>"
};
})
html:
<div foo>
Some Content Here
</div>
result:
<div foo>
<div>the template</div>
<div ng-transclude>Some Content Here</div>
</div>
here's a plnkr
source: https://www.accelebrate.com/blog/angularjs-transclusion-part-1/
Your template must contain an element with an ng-transclude attribute. That's where the body will be "pasted" by angular.
See
https://docs.angularjs.org/api/ng/directive/ngTransclude

How to write an angularjs directive that makes use of both scope and attributes and refer it thru compiled partial?

I want to write a directive which takes advantage of custom attributes, as follows:
<plant-stages
title="Exploration<br/>du cycle de<br/>développement<br/>de la plante"
></plant-stages>
The controller is currently as follows:
app.directive('plantStages', function () {
return {
restrict: 'AE',
templateUrl: 'corn.figure.plant.stages.html',
link: function (scope, element, attrs) {
scope.title = attrs.title;
}
};
});
The partial is as follows:
<figure class="cornStages">
<div>
<p>{{title}}</p>
</div>
<div ng-repeat="stage in stages">
<div class="stage{{stage.stage}}"></div>
<div>
BBCH : {{stage.bbch}}<br/>
{{stage.displayName}}
</div>
</div>
</figure>
The partial makes use of some scope model variables.
And {{title}} should support plain HTML injection out of the view which embeds it, hence should be compiled. I tried to support this but without success.
What modification should I make to have the HTML compiled?
A bonus question: when I pass the attribute in, I create a dummy title variable in the scope that persists where it should only be local. How would one make changes to handle this?
If you want to wrap HTML in your custom directive take a look at the transclude option (see docs):
module.directive('myDirective', function() {
return {
restrict: 'E',
transclude: true,
template: '<div ng-transclude></div>'
};
});
This enables you to place HTML within the directive tag which can be used in the template:
<div ng-controller="Controller">
<my-directive>
<h1>Test</h1>
</my-directive>
</div>
In case you really want to pass HTML via an attribute use ng-bind-html. This requires the ngSanitize module:
module.directive('myDirective', function () {
return {
restrict: 'E',
template: '<div ng-bind-html="title"></div>',
scope: {
title:'#'
}
};
});
I added this to your fiddle.

Replace ng-include node with template?

Kinda new to angular. Is it possible to replace the ng-include node with the contents of the included template? For example, with:
<div ng-app>
<script type="text/ng-template" id="test.html">
<p>Test</p>
</script>
<div ng-include src="'test.html'"></div>
</div>
The generated html is:
<div ng-app>
<script type="text/ng-template" id="test.html">
<p>Test</p>
</script>
<div ng-include src="'test.html'">
<span class="ng-scope"> </span>
<p>Test</p>
<span class="ng-scope"> </span>
</div>
</div>
But what I want is:
<div ng-app>
<script type="text/ng-template" id="test.html">
<p>Test</p>
</script>
<p>Test</p>
</div>
I had this same issue and still wanted the features of ng-include to include a dynamic template. I was building a dynamic Bootstrap toolbar and I needed the cleaner markup for the CSS styles to be applied properly.
Here is the solution that I came up with for those who are interested:
HTML:
<div ng-include src="dynamicTemplatePath" include-replace></div>
Custom Directive:
app.directive('includeReplace', function () {
return {
require: 'ngInclude',
restrict: 'A', /* optional */
link: function (scope, el, attrs) {
el.replaceWith(el.children());
}
};
});
If this solution were used in the example above, setting scope.dynamicTemplatePath to 'test.html' would result in the desired markup.
So thanks to #user1737909, I've realized that ng-include is not the way to go. Directives are the better approach and more explicit.
var App = angular.module('app', []);
App.directive('blah', function() {
return {
replace: true,
restrict: 'E',
templateUrl: "test.html"
};
});
In html:
<blah></blah>
I had the same problem, my 3rd party css stylesheet didn't like the extra DOM-element.
My solution was super-simple. Just move the ng-include 1 up. So instead of
<md-sidenav flex class="md-whiteframe-z3" md-component-id="left" md-is-locked-open="$media('gt-md')">
<div ng-include="myService.template"></span>
</md-sidenav>
I simply did:
<md-sidenav flex class="md-whiteframe-z3" md-component-id="left" md-is-locked-open="$media('gt-md')" ng-include="myService.template">
</md-sidenav>
I bet this will work in most situations, even tho it technically isn't what the question is asking.
Another alternative is to write your own simple replace/include directive e.g.
.directive('myReplace', function () {
return {
replace: true,
restrict: 'A',
templateUrl: function (iElement, iAttrs) {
if (!iAttrs.myReplace) throw new Error("my-replace: template url must be provided");
return iAttrs.myReplace;
}
};
});
This would then be used as follows:
<div my-replace="test.html"></div>
This is the correct way of replacing the children
angular.module('common').directive('includeReplace', function () {
return {
require: 'ngInclude',
restrict: 'A',
compile: function (tElement, tAttrs) {
tElement.replaceWith(tElement.children());
return {
post : angular.noop
};
}
};
});
Following directive extends ng-include native directive functionality.
It adds an event listener to replace the original element when content is ready and loaded.
Use it in the original way, just add "replace" attribute:
<ng-include src="'src.html'" replace></ng-include>
or with attribute notation:
<div ng-include="'src.html'" replace></div>
Here is the directive (remember to include 'include-replace' module as dependency):
angular.module('include-replace', []).directive('ngInclude', function () {
return {
priority: 1000,
link: function($scope, $element, $attrs){
if($attrs.replace !== undefined){
var src = $scope.$eval($attrs.ngInclude || $attrs.src);
var unbind = $scope.$on('$includeContentLoaded', function($event, loaded_src){
if(src === loaded_src){
$element.next().replaceWith($element.next().children());
unbind();
};
});
}
}
};
});
I would go with a safer solution than the one provided by #Brady Isom.
I prefer to rely on the onload option given by ng-include to make sure the template is loaded before trying to remove it.
.directive('foo', [function () {
return {
restrict: 'E', //Or whatever you need
scope: true,
template: '<ng-include src="someTemplate.html" onload="replace()"></ng-include>',
link: function (scope, elem) {
scope.replace = function () {
elem.replaceWith(elem.children());
};
}
};
}])
No need for a second directive since everything is handled within the first one.

ng-switch to change user interface of a directive

I've created a directive for showing a person details:
angular.module('person.directives', []).
directive("person", function() {
return {
restrict: "E",
templateUrl: "person/views/person.html",
replace: true,
scope: {
myPerson: '='
},
link: function (scope, element, attrs) {
}
}
});
the view:
<div>
<span>FirstName: {{myPerson.firstName}}</span><span>LastName: {{myPerson.lastName}} </span>
</div>
The way it's called:
<person my-person="mandat.Person"></person>
mandat being a property of the parent controller.
Now, if myPerson is null, the UI for the directive should show a search button instead of the person details.
What's the best way for doig that ? Can I use the ng-switch statement ? How would I use it in this particular case ?
I found out... I use ng-show and ng-hide
<div>
<div ng-show="myPerson">
<span>FirstName: {{myPerson.firstName}}</span><span>LastName: {{myPerson.lastName}}</span>
</div>
<div ng-hide="myPerson">
<button>search</button>
</div>

Resources