Avoid using extra DOM nodes when using nginclude - angularjs

I'm struggling to wrap my mind around how to have an ng-include not use an extra DOM element as I'm building an angular app from a plain-HTML demo. I'm working with pretty slim HTML with fully developed, tightly DOM-coupled CSS (built from SASS) and refactoring is something I want to avoid at all costs.
Here's the actual code:
<div id="wrapper">
<header
ng-controller="HeaderController"
data-ng-class="headerType"
data-ng-include="'/templates/base/header.html'">
</header>
<section
ng-controller="SubheaderController"
data-ng-class="subheaderClass"
ng-repeat="subheader in subheaders"
data-ng-include="'/templates/base/subheader.html'">
</section>
<div
class="main"
data-ng-class="mainClass"
data-ng-view>
</div>
</div>
I need <section> to be a repeating element but have its own logic and different content. Both, content and number of repetitions are dependent on business logic. As you can see, putting the ng-controller and the ng-repeat on the <section> element will not work. What would, however, is to insert a new DOM node, which is what I'm trying to avoid.
What am I missing out? Is this best practice or is there a better way?
EDIT: just to clarify as asked in comments, the final HTML I'm trying to generate would be:
<div id="wrapper">
<header>...</header>
<section class="submenuX">
some content from controller A and template B (e.g. <ul>...</ul>)
</section>
<section class="submenuY">
different content from same controller A and template B (e.g. <div>...</div>)
</section>
<section class="submenuZ">
... (number of repetitions is defined in controller A e.g. through some service)
</section>
<div>...</div>
</div>
The reason I want to use the same template B (subheader.html), is for code cleanliness. I conceive subheader.html to have some kind of ng-switch in order to return dynamic content.
But basically, the underlaying quiestion is: is there a way to include the contents of a template transparently, without using a DOM node?
EDIT2: The solution needs to be reusable. =)

Some of the other answers suggest replace:true, but keep in mind that replace:true in templates is marked for deprecation.
Instead, in an answer to a similar question, we find an alternative: It allows you to write:
<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());
}
};
});
(cut'n'paste from the other answer)

Edit: After some research and for the sake of completeness, I've added some info. Since 1.1.4, the following works:
app.directive('include',
function () {
return {
replace: true,
restrict: 'A',
templateUrl: function (element, attr) {
return attr.pfInclude;
}
};
}
);
Usage:
<div include="'path/to/my/template.html'"></div>
There is, however, one gotcha: the template cannot be dynamic (as in, passing a variable through scope because $scope, or any DI for that matter, is not accessible in templateUrl - see this issue), only a string can be passed (just like the html snippet above). To bypass that particular issue, this piece of code should do the trick (kudos to this plunker):
app.directive("include", function ($http, $templateCache, $compile) {
return {
restrict: 'A',
link: function (scope, element, attributes) {
var templateUrl = scope.$eval(attributes.include);
$http.get(templateUrl, {cache: $templateCache}).success(
function (tplContent) {
element.replaceWith($compile(tplContent.data)(scope));
}
);
}
};
});
Usage:
<div include="myTplVariable"></div>

You can create a custom directive, linking to the template with the templateUrl property, and setting replace to true:
app.directive('myDirective', function() {
return {
templateUrl: 'url/to/template',
replace: true,
link: function(scope, elem, attrs) {
}
}
});
That would include the template as-is, without any wrapper element, without any wrapper scope.

For anyone who happens to visit this question:
As of angular 1.1.4+ you can use a function in the templateURL to make it dynamic.
Check out this other answer here

With the right setup, you can define your own ngInclude directive that can run instead of the one provided by Angular.js and prevent the built-in directive to execute ever.
To prevent the Angular-built-in directive from executing is crucial to set the priority of your directive higher than that of the built-in directive (400 for ngInclude and set the terminal property to true.
After that, you need to provide a post-link function that fetches the template and replaces the element's DOM node with the compiled template HTML.
A word of warning: this is rather draconian, you redefine the behavior of ngInclude for your whole application. I therefore set the directive below not on myApp but inside one of my own directives to limit its scope. If you want to use it application-wide, you might want to make its behavior configurable, e.g. only replace the element if a replace attribute is set in the HTML and per default fall back to setting innerHtml.
Also: this might not play well with animations. The code for the original ngInclude-directive is way longer, so if you use animations in your application, c&p the original code and shoehorn the `$element.replaceWith() into that.
var includeDirective = ['$http', '$templateCache', '$sce', '$compile',
function($http, $templateCache, $sce, $compile) {
return {
restrict: 'ECA',
priority: 600,
terminal: true,
link: function(scope, $element, $attr) {
scope.$watch($sce.parseAsResourceUrl($attr.src), function ngIncludeWatchAction(src) {
if (src) {
$http.get(src, {cache: $templateCache}).success(function(response) {
var e =$compile(response)(scope);
$element.replaceWith(e);
});
}
});
}
};
}];
myApp.directive('ngInclude', includeDirective);

Related

How to use an AngularjS View (.html file) without using it's tags

Inside a big AngularJS application I have a new HTML template file and a controller for it, and I'd like to build a layout the designer gave me using this temporary view, since I'd like to be able to call some data from the $scope object.
I also created a new route for it so that I have a clean working space.
But I don't want to include it in the main index.html file, like so:
<my-new-template></my-new-template>
I'd just like to start using it without having to include this HTML element anywhere, is this possible? This is the controller so far:
.directive('portfolio', [
function () {
return {
templateUrl: "views/temporary-view.html",
scope: {
data: "="
},
link: function (scope, element, attrs, ctrl) {
scope.stuff = 'stuff';
}
};
}])
The view:
<nav class="portfolio-view">
{{stuff}}
</nav>
Thanks for helping a noob like me! :)
In your directive, you can change the restrict option to change how the directive is called in the HTML. There are 4 options for this. I found this in the AngularJS documentation for directives:
restrict
String of subset of EACM which restricts the directive to a specific directive declaration style. If omitted, the defaults (elements and attributes) are used.
E - Element name (default): <my-directive></my-directive>
A - Attribute (default): <div my-directive="exp"></div>
C - Class: <div class="my-directive: exp;"></div>
M - Comment: <!-- directive: my-directive exp -->
By default, it uses EA, so as an Element (the way you do not want to call it in HTML) or an attribute.
If you wish to change it to, say, class for example, then change your directive definition to:
.directive('portfolio', [
function () {
return {
restrict: 'C',
templateUrl: "views/temporary-view.html",
scope: {
data: "="
},
link: function (scope, element, attrs, ctrl) {
scope.stuff = 'stuff';
}
};
}])
and you can call it as so:
<div class="portfolio"></div>
I think this is what you mean, and I hope it helps!
So I just changed .directive to .controller and posted it alongside the other controllers in the app and not the directives... I guess I was confused with that!
.controller('PortfolioView', ["$scope",
function ($scope) {
$scope.stuff = 'stuff';
}])

Angularjs Interpolation using double curly braces not working under ng-if

UPDATE1: developed the plunker sample that will reproduce the problem. See below.
I have a strange problem in my project, where it appears in one place only. Finally, I was able to reproduce the problem using plunker sample:
http://plnkr.co/edit/JJbq54?p=preview
In the above sample, see the section "With ng-if" and "Without ng-if", enter something in the input text, and see how the double curly braces not working under ng-if, but ng-bind works fine. Also, if you remove check-if-required from the template sites-and-improvements.html also the problem is solved.
More details below:
I have the the following HTML5 code block:
<div ng-if="isFullCUSPAP" id="sites_and_imrpovements_comments">
<div class="form-row">
<div class="inputs-group">
<label>WIND TURBINE:</label>
<div class="input-binary">
<label>
<input type="radio" id="wind_turbine"
name="wind_turbine"
ng-model="$parent.wind_turbine"
value="Yes" force-model-update />
Yes
</label>
</div>
<div class="input-binary">
<label>
<input type="radio" id="wind_turbine"
name="wind_turbine"
ng-model="$parent.wind_turbine"
value="No" force-model-update />
No
</label>
</div>
<span ng-bind="wind_turbine"></span>
<span>wind_turbine = {{wind_turbine}}</span>
</div>
</div>
</div>
I know that ng-if will create a new child scope. See above code, scope variable wind_trubine. Only in this HTML5 file, the curly braces {{}} is not working. However, if I use ng-bind it works fine. In other HTML5 files, I have no problem what so ever. This HTML5 is implemented using directive as follows:
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:true,
templateUrl: '<path-to-file>/site-and-improvments.html',
link: function (scope, elem, attrs) {
//Business Logic for Sites and Improvements
}
}
})
And, simply, I put it in the parent as follows:
<sites-and-improvements></sites-and-improvements>
The only difference I could see, is that this implementation has two levels of nested ng-if, which would look like the following:
<div ng-if="some_expression">
...
...
<sites-and-improvements></sites-and-improvements>
...
...
</div>
Based on comments, I used controller As notation and defined MainController accordingly. See snapshots below. It seems there is a problem if ng-if is nested with two levels. The scope variable is completely confused. I don't get the same results using ng-bind and double curly braces.
If you examine the above snapshots, even though I used controller As notation, you will see that ng-bind gives different results when compared with interpolation using {{}}.
I even changed the default value of wind_turbine to be set as follows in the link function:
scope.MainController.wind_turbine = 'Yes';
I noticed that on page load, everything looks fine, but when I change the value of the input element wind_trubine using the mouse, all related reference are updated correctly except the one that uses {{}}.
Maybe this is because there are two nested levels of ng-if?
Appreciate your feedback.
Tarek
Remove the replace: true from the sites-and-improvements directive:
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
̶r̶e̶p̶l̶a̶c̶e̶:̶t̶r̶u̶e̶,̶
templateUrl: 'site-and-improvments.html',
link: function (scope, elem, attrs) {
//debugger;
}
}
})
It is fighting the check-if-required directive:
app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
return {
priority: 2000,
terminal: true,
link: function (scope, el, attrs) {
el.removeAttr('check-if-required');
$timeout(function(){
//debugger;
$(':input', el).each(function(key, child) {
if (child && child.id === 'test_me') {
angular.element(child).attr('ng-required', 'true');
}
if (child && child.id === 'testInput1') {
//debugger;
//angular.element(child).attr('ng-required', 'true');
}
});
$compile(el, null, 2000)(scope);
})
}
};
}])
The DEMO on PLNKR.
replace:true is Deprecated
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API - replace deprecated
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636
For more information, see Explain replace=true in Angular Directives (Deprecated)
Another solution posted by AngularJS team here:
https://github.com/angular/angular.js/issues/16140#issuecomment-319332063
Basically, they recommend to convert the link() function to use compile() function instead. Here is the update code:
app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
return {
priority: 2000,
terminal: true,
compile: function (el, attrs) {
el.removeAttr('check-if-required');
var children = $(':input', el);
children.each(function(key, child) {
if (child && child.id === 'test_me') {
angular.element(child).attr('ng-required', 'true');
}
});
var compiled = $compile(el, null, 2000);
return function( scope ) {
compiled( scope );
};
}
};
}]).directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:true,
templateUrl: 'site-and-improvments.html'
}
});
The main problem I have with this solution is that I am using the scope parameter which is passed to the link() function. For example, in the .each() loop above, I need to get the value of the element ID which is based on interpolation using {{<angular expre>}}.
So I tried to use pre-link and post-link within the compile function where the scope is available. I noticed that the section with ng-if is removed when execution is in pre-link and then it is added shortly after that. So I had to use $watch to monitor changes to the children to run the needed process when required. I developed this plunker sample:
http://plnkr.co/edit/lsJvhr?p=preview
Even after all such effort, the issue is not resolved. So the bottom line for similar cases, is that if you need to use the scope then you have to remove replace: true.
Any feedback would be appreciated.
Tarek

AngularJS : directives which take a template through a configuration object, and show that template multiple times

I'm looking to create a custom directive that will take a template as a property of a configuration object, and show that template a given number of times surrounded by a header and footer. What's the best approach to create such a directive?
The directive would receive the configuration object as a scope option:
var app = angular.module('app');
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '=?'
}
...
}
}
This object (called config) is passed optionally to the directive using two way binding, as show in the code above. The configuration object can include a template and a number indicating the number of times the directive should show the template. Consider, for example, the following config object:
var config = {
times: 3,
template: '<div>my template</div>'
};
It would, when passed to the directive, cause the directive to show the template five times (using an ng-repeat.) The directive also shows a header and a footer above and below the template(s):
<div>the header</div>
<div>my template</div>
<div>my template</div>
<div>my template</div>
<div>the footer</div>
What's the best way to implement this directive? Note: When you reply, please provide a working example in a code playground such as Plunker, as I've run into problems with each possible implementation I've explored.
Update, the solutions I've explored include:
The use of the directive's link function to append the head, template with ng-repeat, and footer. This suffers from the problem of the template not being repeated, for some unknown reason, and the whole solutions seems like a hack.
The insertion of the template from the configuration object into middle of the template of the directive itself. This proves difficult because jqLite seems to have removed all notion of a CSS selector from its jQuery-based API, leading me to wonder if this solution is "the Angular way."
The use of the compile function to build out the template. This seems right to me, but I don't know if it will work.
You could indeed use ng-repeat but within your directive template rather than manually in the link (as that wouldn't be compiled, hence not repeated).
One question you didn't answer is, should this repeated template be compiled and linked by Angular, or is it going to be static HTML only?
.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
config: '=?'
},
templateUrl: 'myTemplate',
link: function(scope) {
scope.array = new Array(config.times);
}
}
}
With myTemplate being:
<header>...</header>
<div ng-repeat="item in array" ng-bind-html="config.template"></div>
<footer>...</footer>
I'd think to use ng-transclude in this case, because the header & footer wrapper will be provided by the directive the inner content should change on basis of condition.
Markup
<my-directive>
<div ng-repeat="item in ['1','2','3']" ng-bind-html="config.template| trustedhtml"><div>
</my-directive>
Directive
var app = angular.module('app');
app.directive('myDirective', function($sce) {
return {
restrict: 'E',
transclude: true,
template: '<div>the header</div>'+
'<ng-transclude></ng-transclude>'+
'<div>the footer</div>',
scope: {
config: '=?'
}
.....
}
}
Filter
app.filter('trustedhtml', function($sce){
return function(val){
return $sce.trustedHtml(val);
}
})

How can I include HTML into a page with AngularJS from an HTML file?

I have some code that I have taken over. It looks like this:
<div id="init"
data-admin-template
ng-show="ss.display.init"
data-template-url="init.html">
</div>
There's a directive adminTemplate:
app.directive('adminTemplate', ['stateService', function (stateService) {
return {
restrict: 'A',
templateUrl: function (elem, attrs) {
return "/Content/app/admin/templates/" + attrs.templateUrl;
},
link: function (scope, element, attrs) {
scope.stateService = stateService;
}
};
}]);
I am not very familiar with templates and it seems that this directive does not really do much. Is there a way I can do this without the directive?
As #Oli said in the comments, ng-include is definitely one solution. There are others as well - angular likes to offer you just enough options to leave you doubting yourself - but i'm wondering what you'd actually gain by changing it.
With ng-include you'd need to add a controller to to your html, to supply stateService (if you don't do it here, you'd have to add it to every different admin template). So you'd end up with:
<div id="init"
ng-include="/Content/app/admin/templates/init.html"
ng-show="ss.display.init"
ng-controller="AdminController">
</div>
So you end up with the same amount of attributes, you need the whole template path and it becomes less readable. Looking at what you have now, it's clear to see the intent.
You could also go one step further and give it the flexibility of being an element or attribute
<admin-template
id="init"
ng-show="ss.display.init"
data-template-url="init.html">
</admin-template>
app.directive('adminTemplate', ['stateService', function (stateService) {
return {
restrict: 'EA',
templateUrl: function (elem, attrs) {
return "/Content/app/admin/templates/" + attrs.templateUrl;
},
link: function (scope, element, attrs) {
scope.stateService = stateService;
}
};
}]);
It may appear to be doing very little, but my feeling is that the previous developer has refactored out repetition, to get to this little directive. I'd argue that as it is it removes boiler plate, allows easy re-usability and communicates intent well in your mark up.

Link vs compile vs controller

When you create a directive, you can put code into the compiler, the link function or the controller.
In the docs, they explain that:
compile and link function are used in different phases of the angular
cycle
controllers are shared between directives
However, for me it is not clear, which kind of code should go where.
E.g.: Can I create functions in compile and have them attached to the scope in link or only attach functions to the scope in the controller?
How are controllers shared between directives, if each directive can have its own controller? Are the controllers really shared or is it just the scope properties?
Compile :
This is the phase where Angular actually compiles your directive. This compile function is called just once for each references to the given directive. For example, say you are using the ng-repeat directive. ng-repeat will have to look up the element it is attached to, extract the html fragment that it is attached to and create a template function.
If you have used HandleBars, underscore templates or equivalent, its like compiling their templates to extract out a template function. To this template function you pass data and the return value of that function is the html with the data in the right places.
The compilation phase is that step in Angular which returns the template function. This template function in angular is called the linking function.
Linking phase :
The linking phase is where you attach the data ( $scope ) to the linking function and it should return you the linked html. Since the directive also specifies where this html goes or what it changes, it is already good to go. This is the function where you want to make changes to the linked html, i.e the html that already has the data attached to it. In angular if you write code in the linking function its generally the post-link function (by default). It is kind of a callback that gets called after the linking function has linked the data with the template.
Controller :
The controller is a place where you put in some directive specific logic. This logic can go into the linking function as well, but then you would have to put that logic on the scope to make it "shareable". The problem with that is that you would then be corrupting the scope with your directives stuff which is not really something that is expected.
So what is the alternative if two Directives want to talk to each other / co-operate with each other? Ofcourse you could put all that logic into a service and then make both these directives depend on that service but that just brings in one more dependency. The alternative is to provide a Controller for this scope ( usually isolate scope ? ) and then this controller is injected into another directive when that directive "requires" the other one. See tabs and panes on the first page of angularjs.org for an example.
I wanted to add also what the O'Reily AngularJS book by the Google Team has to say:
Controller - Create a controller which publishes an API for communicating across directives. A good example is Directive to Directive Communication
Link - Programmatically modify resulting DOM element instances, add event listeners, and set up data binding.
Compile - Programmatically modify the DOM template for features across copies of a directive, as when used in ng-repeat. Your compile function can also return link functions to modify the resulting element instances.
A directive allows you to extend the HTML vocabulary in a declarative fashion for building web components. The ng-app attribute is a directive, so is ng-controller and all of the ng- prefixed attributes. Directives can be attributes, tags or even class names, comments.
How directives are born (compilation and instantiation)
Compile: We’ll use the compile function to both manipulate the DOM before it’s rendered and return a link function (that will handle the linking for us). This also is the place to put any methods that need to be shared around with all of the instances of this directive.
link: We’ll use the link function to register all listeners on a specific DOM element (that’s cloned from the template) and set up our bindings to the page.
If set in the compile() function they would only have been set once (which is often what you want). If set in the link() function they would be set every time the HTML element is bound to data in the
object.
<div ng-repeat="i in [0,1,2]">
<simple>
<div>Inner content</div>
</simple>
</div>
app.directive("simple", function(){
return {
restrict: "EA",
transclude:true,
template:"<div>{{label}}<div ng-transclude></div></div>",
compile: function(element, attributes){
return {
pre: function(scope, element, attributes, controller, transcludeFn){
},
post: function(scope, element, attributes, controller, transcludeFn){
}
}
},
controller: function($scope){
}
};
});
Compile function returns the pre and post link function. In the pre link function we have the instance template and also the scope from the controller, but yet the template is not bound to scope and still don't have transcluded content.
Post link function is where post link is the last function to execute. Now the transclusion is complete, the template is linked to a scope, and the view will update with data bound values after the next digest cycle. The link option is just a shortcut to setting up a post-link function.
controller: The directive controller can be passed to another directive linking/compiling phase. It can be injected into other directices as a mean to use in inter-directive communication.
You have to specify the name of the directive to be required – It should be bound to same element or its parent. The name can be prefixed with:
? – Will not raise any error if a mentioned directive does not exist.
^ – Will look for the directive on parent elements, if not available on the same element.
Use square bracket [‘directive1′, ‘directive2′, ‘directive3′] to require multiple directives controller.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope, $element) {
});
app.directive('parentDirective', function() {
return {
restrict: 'E',
template: '<child-directive></child-directive>',
controller: function($scope, $element){
this.variable = "Hi Vinothbabu"
}
}
});
app.directive('childDirective', function() {
return {
restrict: 'E',
template: '<h1>I am child</h1>',
replace: true,
require: '^parentDirective',
link: function($scope, $element, attr, parentDirectCtrl){
//you now have access to parentDirectCtrl.variable
}
}
});
Also, a good reason to use a controller vs. link function (since they both have access to the scope, element, and attrs) is because you can pass in any available service or dependency into a controller (and in any order), whereas you cannot do that with the link function. Notice the different signatures:
controller: function($scope, $exceptionHandler, $attr, $element, $parse, $myOtherService, someCrazyDependency) {...
vs.
link: function(scope, element, attrs) {... //no services allowed
this is a good sample for understand directive phases
http://codepen.io/anon/pen/oXMdBQ?editors=101
var app = angular.module('myapp', [])
app.directive('slngStylePrelink', function() {
return {
scope: {
drctvName: '#'
},
controller: function($scope) {
console.log('controller for ', $scope.drctvName);
},
compile: function(element, attr) {
console.log("compile for ", attr.name)
return {
post: function($scope, element, attr) {
console.log('post link for ', attr.name)
},
pre: function($scope, element, attr) {
$scope.element = element;
console.log('pre link for ', attr.name)
// from angular.js 1.4.1
function ngStyleWatchAction(newStyles, oldStyles) {
if (oldStyles && (newStyles !== oldStyles)) {
forEach(oldStyles, function(val, style) {
element.css(style, '');
});
}
if (newStyles) element.css(newStyles);
}
$scope.$watch(attr.slngStylePrelink, ngStyleWatchAction, true);
// Run immediately, because the watcher's first run is async
ngStyleWatchAction($scope.$eval(attr.slngStylePrelink));
}
};
}
};
});
html
<body ng-app="myapp">
<div slng-style-prelink="{height:'500px'}" drctv-name='parent' style="border:1px solid" name="parent">
<div slng-style-prelink="{height:'50%'}" drctv-name='child' style="border:1px solid red" name='child'>
</div>
</div>
</body>
compile: used when we need to modify directive template, like add new expression, append another directive inside this directive
controller: used when we need to share/reuse $scope data
link: it is a function which used when we need to attach event handler or to manipulate DOM.

Resources