Template for directive must have exactly one root element - javascript

I am new to angularJs. I am trying to create new directive which contains input element and a button. I want to use this directive to clear input text when button is clicked.
When I use my directive in html I am getting below error :
Error: [$compile:tplrt] Template for directive 'cwClearableInput' must have exactly one root element.
html:
<div class="input-group">
<cw-clearable-input ng-model="attributeName"></cw-clearable-input>
</div>
clearable_input.js:
angular.module('cw-ui').directive('cwClearableInput', function() {
return {
restrict: 'EAC',
require: 'ngModel',
transclude: true,
replace: true,
template: '<input type="text" class="form-control"/><span class="input-group-btn"><button type="button" class="btn" ng-click="" title="Edit"><span class="glyphicon-pencil"></span></button></span>',
controller: function( $scope ) {
}
};
});
I am not able to figure it out how to achieve this.

Well, the error is pretty self-explanatory. Your template needs to have a single root and yours has two. The simplest way to resolve this would be to just wrap the whole thing in a div or a span:
template: '<div><input type="text" class="form-control"/><span class="input-group-btn"><button type="button" class="btn" ng-click="" title="Edit"><span class="glyphicon-pencil"></span></button></span></div>',
Before:
<input type="text" class="form-control"/>
<span class="input-group-btn">
<button type="button" class="btn" ng-click="" title="Edit">
<span class="glyphicon-pencil"></span>
</button>
</span>
After:
<div> <!-- <- one root -->
<input type="text" class="form-control"/>
<span class="input-group-btn">
<button type="button" class="btn" ng-click="" title="Edit">
<span class="glyphicon-pencil"></span>
</button>
</span>
</div>

This error will also occur if the path to the template is incorrect, in which case the error is everything but explanatory.
I was referring the template from within templates/my-parent-template.html with the (incorrect)
template-url="subfolder/my-child-template.html"
I changed this to
template-url="templates/subfolder/my-child-template.html"
which solved it.

Just wrap your template in something:
template: '<div><input type="text" class="form-control"/><span class="input-group-btn"><button type="button" class="btn" ng-click="" title="Edit"><span class="glyphicon-pencil"></span></button></span></div>',

If you don't want to make one root element you can instead set replace to false. In your directive you are setting it to true, which replaces the directive tag "cw-clearable-input" with the root element of your directive.

If you have a comment in your template alongside the root element in the directive template, you will see the same error.
Eg. If your directive template has HTML like this (with "replace" set to true in the directive JS), you will see the same error
<!-- Some Comment -->
<div>
<p>Hello</p>
</div>
So to resolve this in scenarios where you want to retain the comments you will need to move the comment so that it is inside the template root element.
<div>
<!-- Some Comment -->
<p>Hello</p>
</div>

When none of this worked for me, prefixing a / to the template path fixed the issue in my case.
function bsLoginModal() {
return {
restrict: 'A',
templateUrl:'/app/directives/bootstrap-login-modal/template.html',
link: linkFunction,
controller: SignInCtrl,
controllerAs: 'vm'
}
}
instead of
templateUrl:'app/directives/bootstrap-login-modal/template.html
Good Luck.

Check your template or templateUrl
templateUrl: 'some/url/here.html'
template: '<div>HTML directly goes here</div>'

My problem was that webpack's HtmlWebpackPlugin was appending a script tag to the directive content. So angular was seeing:
<div>stuff</div>
<script type="text/javascript" src="directives/my-directive"></script>
The solution is to use HtmlWebpackPlugin's inject option in your webpack.config.js:
new HtmlWebpackPlugin({
template: './src/my-directive.html',
filename: './src/my-directive.html',
inject: false
}),

Related

AngularJS directive not updating ng-model on textarea in IE 11

Running into a weird issue and not able to figure it out. Basically, I have a directive that manages a dropdown, and when someone chooses the "Other" option, it displays a that tracks a string value. It works perfectly fine in most browsers, but for some reason in IE11 the ng-model in the textarea element never updates in the bound controller (and subsequently doesn't update in any parent controllers either).
vm.selected (the lr-plan-picker attribute) is a javascript object with an id and body property. I've tried commenting out the hidden input elements I was using, as well as wrapped the in an ng-if separately, but that didn't work either. In the following example, I don't ever see any value in the <span ng-bind-html="vm.selected.body"></span> element. Also, the code never executes the $watch on this.selected.body, so for some reason the digest isn't triggering to let this scope know there has been changes to the DOM?
I appreciate any insight you can give in advance, I'm hoping I've missed something super obvious, because I feel like I've tried everything.
Directive/Controller:
function lrPlanPicker() {
return {
scope: {
selected: '=lrPlanPicker',
provider: '=',
disabled: '=',
name: '#',
onSelect: '&',
optedOut: '='
},
restrict: 'A',
templateUrl: require('#/src/app/referral/partials/plan-picker.tpl.html'),
controllerAs: 'vm',
controller: PlanPickerController,
bindToController: true
};
}
class PlanPickerController {
constructor($scope) {
$scope.$watch(() => {
return this.selected ? this.selected.body : null;
}, function (newVal) {
console.log(newVal);
});
this.init();
}
selectPlan(plan) {
if (plan === 'other') {
plan = {
id: 0,
body: '',
name: 'Other'
};
} else {
plan.body = plan.id;
}
this.selected = plan;
this.onSelect({ plan: plan });
}
}
Directive Template (plan-picker.tpl.html):
<div class="lr-plan-picker">
<input type="hidden"
ng-attr-name="{{vm.name}}"
ng-model="vm.selected.id"
ng-if="vm.selected.id !== 0 && !vm.optedOut"
required />
<input type="hidden"
ng-attr-name="{{vm.name}}"
ng-model="vm.selected.body"
ng-if="vm.selected.id === 0 && !vm.optedOut"
required />
<div uib-dropdown class="btn-group">
<button type="button" uib-dropdown-toggle class="btn btn-primary dropdown-toggle" ng-disabled="vm.disabled">
{{ vm.selected && vm.selected.name ? vm.selected.name : 'Select Plan' }} <span class="caret"></span>
</button>
<a lr-plan-preview="vm.selected" ng-if="vm.selected && vm.selected.id" class="plan-helper">
<i class="fa fa-question-circle-o fa-lg text-primary"></i>
</a>
<ul class="dropdown-menu">
<li ng-show="vm.dropdown.loading">
<div class="text-center"><i class="fa fa-spin fa-spinner"></i></div>
</li>
<li ng-repeat="plan in vm.dropdown.plans">
<a ng-click="vm.selectPlan(plan)">{{ plan.name }}</a>
</li>
<li ng-if="!vm.dropdown.loading">
<a ng-click="vm.selectPlan('other')">Other</a>
</li>
</ul>
</div>
<textarea ng-if="vm.selected.id === 0 && !vm.optedOut"
ng-model="vm.selected.body"
ng-disabled="vm.disabled"
required
maxlength="100"
ng-attr-name="{{vm.name}}-other"
class="form-control m-t-sm"></textarea>
<span ng-bind-html="vm.selected.body"></span>
</div>
Template using the directive (controller.tpl.html):
<div lr-plan-picker="vm.selectedPlan"
name="selectedPlan"
provider="vm.selectedProvider"
on-select="vm.onSelectPlan(plan)"
disabled="vm.isOptedOut || vm.isDontKnow"
opted-out="vm.isOptedOut">
</div>
UPDATE:
So, by process of elimination (i.e. I kept removing code until it started working), it seems like the disabled attribute is what's causing the issue. When I remove the disabled attribute from the controller.tpl.html template, the ng-model is respected. Not sure what's causing this, but when I changed the directive property from disabled to isDisabled and updated my usage of the directive, it seems to work. I don't have time to figure out why IE11 is doing something weird with directives and the disabled property, but if anyone has any insight I'd love to know. I'll mark this as Answered in a few days once it allows me to.
So, by process of elimination (i.e. I kept removing code until it started working), it seems like the disabled attribute is what's causing the issue. When I remove the disabled attribute from the controller.tpl.html template, the ng-model is respected. Not sure what's causing this, but when I changed the directive property from disabled to isDisabled and updated my usage of the directive, it seems to work. I don't have time to figure out why IE11 is doing something weird with directives and the disabled property, but if anyone has any insight I'd love to know.

AngularJS - HTML losing scope on template replace in ng-repeat

I have the following code in my html.
<div id="section">
<div new-exercise ng-repeat="section in sections track by $index">
</div>
</div>
<div class="add_exercise add_exercise_btn">
<a class="add_exercise_link" ng-click="addExercise()">
<span>+ Add an Exercise</span>
</a>
</div>
the method addExercise() adds a new element to the variable sections, hence updating the html with another template (represented by directive new-exercise).
i.e.
$scope.addExercise = function(){
$scope.sections.push({
title: "hello",
content: "fsa"
});
}
The directive new-exercise:
.directive('newExercise', function () {
return {
templateUrl: '/templates/exercise-form.html',
replace: true,
}
})
The template exercise-form.html:
<div class="add_exercise" id="add_exercise_form" data-section-id="{{id}}">
<form class="new_section">
<div class="input-box">
<input id="title" type="text" name="title" class="form-control" value="{{section.title}}" ng-model="section.title">
<label for="title">Exercise Name</label>
<span class="help-block"></span>
<span>{{ section.title }}</span>
</div>
</form>
</div>
I expect the template exercise-form.html to update the value inside input to be hello but the scope is empty.
However, if I remove the directive and add the template html under ng-repeat it works as I expect. I feel that the scope is lost due to directive, but not so sure about the exact reason. Can anyone explain me the reason and how to resolve?
Thanks.
Remove the replace: true in your directive.
Corrected directive given below:
.directive('newExercise', function () {
return {
templateUrl: '/templates/exercise-form.html'
}
})

angular template directive not working

i have this route in angular
when('/customers', {
//templateUrl: 'customers.html'
controller: 'ListController'
, template: "<h1>{{customers}}</h1>"
})
this works and it displays properly.i decide to go a bit bigger
when('/customers', {
//templateUrl: 'customers.html'
controller: 'ListController'
, template: "<div class=\"navigation-section\" id=\"customers\">\r\n <div class=\"section\">\r\n <div class=\"text-input-container card\"> <i class=\"icon-search text-input-icon\"><\/i>\r\n <input type=\"text\" class=\"text-input \" placeholder=\"Search\" ng-model=\"query1\" \/> <\/div>\r\n <div> <\/div>\r\n <\/div>\r\n <div class=\"section\">\r\n <ul class=\"list\">\r\n <li ng-repeat=\"customer in customers | filter:query1 | orderBy:[\'customer.information.name\',\'customer.information.phone\']\" ripple>\r\n <a href=\"#\/customer\/{{customers.indexOf(customer)}}\"><\/a>\r\n <button class=\"icon-button\"><i class=\"icon-account-circle\"><\/i><\/button> <span class=\"item-text\">\r\n\t\t\t{{customer.information.name}}\r\n\t\t\t<span class=\"secondary-text\">\r\n\t\t\t\t{{customer.information.phone}}\r\n\t\t\t<\/span> <\/span> <i class=\"icon-message item-action\"><\/i> <\/li>\r\n <\/ul>\r\n <\/div>\r\n<\/div>"
})
this doesnt work why?
It is better to put the template in a file and load with templateUrl. However if you really, really need to implement it with raw template then you can use single quotes instead of double ones, so you do not have to escape the double quotes inside a template string, so sth like that:
...
template: '<div class="navigate-section"></div>',
...
You can also try and do sth like:
...
template: [
'<div class="navigate-section">',
'<span>Text</span>',
'</div>'
].join('')
...
I think it should work and will increase readabilty a bit.
You can also add templates to cache as for example angular ui boostrap does it when you include single js file with templates, the tpls.js one.
It is sth like this:
angular.module('my/template/name.html', [])
.run(['$templateCache', function($templateCache) {
$templateCache.put(
'my/template/name.html', '<div class="my-class"><span>Text</span></div>'
);
}]);
You can also include in your main html file where your angular app runs the template as script with ng-template so you put
<script type="text/ng-template" id="/template.html">
<p>
My template
</p>
</script>
and then, pass as templateUrl the string you put as id in script tag
...
templateUrl: '/template.html',
...
One of this methods, should resolve your issue. But still I would recomend loading templates for directives from files.
Angular doesn't expect all the escaped whitespace characters you have in your template. Try this version that's encapsulated in single quotes, has \r\n\t's removed, and does not escape backslashes:
when('/customers', {
//templateUrl: 'customers.html'
controller: 'ListController',
template: '<div class="navigation-section" id="customers"><div class="section"><div class="text-input-container card"> <i class="icon-search text-input-icon"></i><input type="text" class="text-input" placeholder="Search" ng-model="query1" /> </div><div> </div></div><div class="section"><ul class="list"><li ng-repeat="customer in customers | filter:query1 | orderBy:[\'customer.information.name\',\'customer.information.phone\']" ripple><button class="icon-button"><i class="icon-account-circle"></i></button> <span class="item-text">{{customer.information.name}}<span class="secondary-text">{{customer.information.phone}}</span> </span> <i class="icon-message item-action"></i></li></ul></div></div>'
})

AngularJS make directive from ngMessages

I want to create a directive which allow me to generate validation message near input - based on ngMessages(as in example).I have this working HTML example:
<div class="field">
<div class="ui right icon input">
<input type="email" name="email" ng-model="vm.user.email" placeholder="E-mail" required>
<i class="at icon"></i>
</div>
<div ng-messages="vm.signUpForm.email.$error" ng-show="vm.signUpForm.$submitted">
<div ng-messages-include="shared/validation/formErrorMessages.html"> </div>
</div>
</div>
My current directive:
var app = angular.module('app.directives', []);
app.directive('formError', function() {
return {
restrict: 'AE',
replace: 'false',
scope: {
statement: '#',
error: '#'
},
template: '<div ng-messages="error" ng-show="true"><div ng-messages-include="shared/validation/formErrorMessages.html"></div></div>'
};
});
And how I tried to run it:
<div form-error error="{{ vm.signUpForm.email.$error }}" statement="{{ vm.signUpForm.$submitted }}"></div>
It's not working - message won't appear - without any error. On message show I will also want to add class 'error' to 'div.field', but it should be easy.
Any idea how to make this directive work or maybe how to handle this in another, more comfortable way?
You made a a mistake, you should pass attributes to directive with
scope: {
statement: '=',
error: '='
},
# biding is for passing string values, not objects and error is an object, so passing it that way will not work as expected. Of course you could use attr.$observe and JSON.parse, but that is not what you wanna do here.
https://plnkr.co/edit/iRRPqLpmqdQltNjw35Nb?p=preview

AngularJS curstom directive and ngRepeat

I'm trying to make an planification app with AngularJS. The main feature is to create a Task.
I want to put the source of the task in a directive :
<section id="runningTasks" ng-controller='RunningTaskCtrl as ctrl'>
<task class="task" ng-repeat='task in ctrl.tasks'></task>
</section>
For each task, I add it in the div.
Here is my directive definition :
.directive('task', function(){
return {
restrict: 'EA',
replace:'true',
templateUrl: '/Planificator/directives/task/task.html',
link : function(scope, element, attrs){
var date = $(element).find(".datepicker");
date.datepicker();
date.datepicker("option", "dateFormat", "dd-mm-yy");
}
};
})
And the content of task.html :
<div class="task" ng-click="task.editting = true" task>
<h1>{{ task.title }}</h1>
<p>
{{ task.comment }}
</p>
<div class="edit-task" ng-show="task.editting">
<form ng-submit="ctrl.propose(task)">
... form stuff ...
</form>
</div>
</div>
My problem is when I run my page, I get an error :
Error: [$compile:multidir] http://errors.angularjs.org/1.2.26/$compile/multidir?p0=task&p1=task&p2=tem…3D%20true%22%20task%3D%22%22%20ng-repeat%3D%22task%20in%20ctrl.tasks%22%3E
at Error (native)
(the clean link : Angular error generator)
I already had this problem before and I just put the content of the template in the ngRepeat and doesn't think anymore, but this time I would like to be things in the good way.
Thank you for the answers !
Your problem is:
<div class="task" ng-click="task.editting = true" task>
Since this is part of the template created from the task directive you are trying to add the task directive over and over.
Change to:
<div class="task" ng-click="task.editting = true">

Categories