angularjs directive required attribute for inner control - javascript

I am struggling with the following problem. We have a directive called deeplink. It has the following code:
restrict: 'E',
require: 'ngModel',
scope: {
smDropdown: '=smDeeplinkDropdown',
settings: '&smDropdownSettings',
onRefresh: '&smOnRefresh',
onModelChange: "&?smOnChange",
disable: "=?",
valueRequired: "=?",
hideNew: "=?",
excludeValue : "=?"
},
templateUrl: 'app/templates/smDeeplinkDropdown',
And the template for the directive is the following:
<div class="row input-group">
<select name="deeplinkDropdown"
class="form-control dropdown deeplinkDropdown"
ng-disabled="disable"
ng-required="valueRequired"
data-ng-model="dropdownModel.key"
data-ng-options="item.key as item.text disable when item.hidden for item in itemList | filter:ngOptionFilter"
data-ng-change="modelChanged(dropdownModel.key)">
<option value="">{{noneSelectedLabel}}</option>
</select>
I only show the relevant info here, not the whole HTML or whole code. Anyway, I'm struggling with the required attribute for that select. In my form I have the following:
<div data-sm-deeplink-dropdown="crud.metaData.vendors"
data-ng-model="crud.model.vendorId"
value-required="true"
name="vendorId"
id="vendorId"
data-sm-dropdown-settings="crud.getVendorSettings()"
data-sm-on-refresh="crud.refreshMetaData()">
</div>
<label class="field-validation-error control-label-error animate-show"
ng-show="form.editPurchaseOrdersGeneralForm.vendorId.$error.required">
#string.Format(Messages.isRequired, Labels.vendor)
</label>
My problem is that the select control doesn't propagate its required error back to the main directive element. So, I can not really see my validation error.
While I was writing this I thought I can probably add an ng-form either in my form or inside the directive to solve this problem. I don't really like adding too many ng-forms but I see no other solution.
What do you think?

Ok, in my form I simply changed div to ng-form and this worked (except for original form loading - it only shows in red with my error when I change to something and then to empty option)

Related

AngularJS Directive Isolated scope inside nested ng-transclude

I've been looking all over the internet for something like this and I still can't find the answer.
I have a directive that is reused throughout my application, it allows the user to sort and search through lists of items. I have multiple kinds of items that can be passed in to the directive (html templates that I pass in) as well as multiple uses for those templates. I.e, sometimes I want a certain button on that template, but sometimes I want another. (This will make more sense in a minute).
Therefore I have created multiple directives with transclusion in order to achieve this. However, I'm having serious issues with scoping and I can't seem to figure out exactly how to pass the isolated scope to the child directive.
Below is my code:
Item List Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function (scope, element, attributes, ctrl, transclude) {
//Things that modify the scope here. This scope is what I want to pass down to the child directives
//NOTE: I do not currently have a working transclude function which is why I didn't include it here because I have no idea what to do with it
scope.pagedItems = groupItemsToPages(items);
scope.currentPage = 0;
};
return {
restrict: 'E',
replace: 'true',
transclude: true,
templateUrl: 'partials/directives/item-list.html',
link: linkFunction,
scope: {
searchPlaceholder: "#",
items: "=",
control: "="
}
};
});
item-list.html
<div class="form-group">
<!-- I won't put all of the html here, just some to show you what i'm going for -->
<div class="search-field">
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}/>
</div>
<table class="table table-hover">
<tbody>
<tr ng-repeat="item in pagedItems[currentPage]">
<td ng-transclude></td>
</tr>
</tbody>
</table>
</div>
Here's the directive that simply returns the URL of whatever template is passed to it. This is so that I can add in an extra html through further nested transclusions.
item-template.js
var app = angular.module('main');
app.directive('itemTemplate', function() {
return {
restrict: 'AE',
replace: 'true',
transclude: true,
templateUrl: function(tElement, tAttrs){
return tAttrs.templateUrl;
}
};
});
Here's an example template (extremely simplified again, just to show you the layout)
profile-template.html
<div>
<p>item.name</p>
<p>item.description</p>
</div>
<div ng-transclude></div>
Here's an example of the HTML that calls this code
tab.html
<div class="tab">
<div class="available-items">
<item-list control="profileControl" search-placeholder="Search Profiles" items="profileControl.profiles">
<item-template template-url="partials/profile-template.html">
<button type="button" ng-click="launchProfile(item.id)">Launch Profile</button>
</item-template>
</item-list>
</div>
</div>
So now that you've seen the code. The issue I'm having is that my profile-template.html inclusion isn't getting the scope from the directive above it even though I've tried cloning the scope to it. All the examples I've seen require you to remove the template key from the directive and assume you're only returning the code you have in your transclusion. In my case, I have other html that I want to display in the template.
Any help would be appreciated
Instead of trying to pass the scope between your directives, you can make use of the $parent attribute and get access to higher scopes.
For instance, in your item-template controller you could gain access to the higher scope from item-list with a code like this:
if ($parent.searchPlaceholder === '') {
...
}

Custom directive with ng-repeat (outside and inside), replace and templateUrl

I've checked various ressources and all leads to a bug that was closed long ago. Here is a similar issue, but I think mine is slightly different and persists up to at least angular.js 1.5.
I'd like to validate, if this is a new angular bug or if I missed something, since I'm relatively new to angular.
My problem
Check this plunker for a short demo of the issue
I've got a directive <infos> rendered inside a ng-repeat block
<body ng-controller="MainCtrl" class="container">
<div ng-repeat="item in items">
<h2>{{item.title}}</h2>
<infos></infos>
</div>
</body>
The <infos> directive has a ng-repeat block on its first element
<div ng-repeat="(infoId, color) in item.infos">
<p style="background: {{color}}">{{color}}</p>
</div>
The directive is loaded with templateUrl and replace:true.
.directive('infos', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'infos.html'
};
})
The unexpected result:
When rendered, the directives of all iterations of the surrounding ng-repeat are rendered on the last ng-repeat of the outside loop and not as expected on each iteration of the surrounding ng-repeat. Check the plunker demo for a better understanding, what's going wrong.
Workarounds
I found several workarounds to the problem:
Wrapping the ng-repeat element in the directive by any other element
<div>
<div ng-repeat="(infoId, color) in item.infos">
<p style="background: {{color}}">{{color}}</p>
</div>
</div>
Leaving away the replace: true
Using template instead of templateUrl. Inlining the template code.
Using <script type="text/ng-template" id="infos.html">... to load the template
There might be another solution, using the $templateCache but I couldn't figure out how to preload the template, yet.

How would I create something similar like this in AngularJS

Having a hard time grasping AngularJs.. How would you build something like this simple textfields and animations in AngularJS? I had been looking into using directives but it just isn't adding up to me much. I had been trying to base this from what I learned online but was not having much luck
http://codepen.io/yusufbkr/pen/RPBQqg
HTML:
<div class="materialContainer">
<div class="title">LOGIN</div>
<div class="input">
<label for="name">Username</label>
<input type="text" name="name" id="name">
<span class="spin"></span>
</div>
<div class="input">
<label for="pass">Password</label>
<input type="password" name="pass" id="pass">
<span class="spin"></span>
</div>
<div class="button login">
<button><span>GO</span> <i class="fa fa-check"></i></button>
</div>
Forgot your password?
<div class="title">REGISTER</div>
<div class="input">
<label for="regname">Username</label>
<input type="text" name="regname" id="regname">
<span class="spin"></span>
</div>
<div class="input">
<label for="regpass">Password</label>
<input type="password" name="regpass" id="regpass">
<span class="spin"></span>
</div>
<div class="input">
<label for="reregpass">Repeat Password</label>
<input type="password" name="reregpass" id="reregpass">
<span class="spin"></span>
</div>
<div class="button">
<button><span>NEXT</span></button>
</div>
just use the codepen link ( http://codepen.io/yusufbkr/pen/RPBQqg ), stackoverflow won't let me input the rest of the code...
Thanks! Any help would be incredible
I'm just learning Angular myself. From what I know, you're on the right track to be thinking of directives for swapping out interactive parts of the DOM. I suspect you'll also be needing ui-router for dealing with the widget outcomes (I hear it's the industry standard, vs the built-in ngRouter).
I'm partly writing this out to make sure I understand it, so I hope other, more knowledgeable people will come by and answer any questions you have about it (or point out where I'm misundertanding something.)
So you bring the webpage into the Angular world by, for instance, making the <html></html> tags into <html ng-app="yourApp"></html>. The unchanging html that will be the same on every view is in the index.html file. Put the html for the boxes is in another html file, say box.html. On the index.html file in the place where you want to have your boxes appear, put <div ui-view></div>. The ui-view connects to ui-router. Down at the bottom of the index.html right before closing the body tag, put
<script src="lib/angular/angular.js"></script>
<script src="lib/angular-ui-router/release/angular-ui-router.js"></script>
<script src="app/app.js"></script>.
In app.js, you put something like,
angular.module('yourApp', [
'yourApp.box',
'ui.router'
])
.config(function($stateProvider, $urlRouterProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/signin/child');
$stateProvider
.state('box', {
url: '/signin',
templateUrl: 'app/box/box.html',
controller: 'BoxController'
})
.state('box.child', {
url: '/child',
template: '<span>{{ definedProperty }}</span>'
});
})
});
Starting at the top, you name angular, call its module function, and give it the parameters ('theNameYouChose', ['dependencies','required'])
In the config bit there, you're telling it about dependencies config needs. $stateProvider and $urlRouterProvider are ui-router things, and $httpProvider is, I believe, an angular thing that is still necessary.
In the urlRouterProvider, you're providing the default address the website goes to: in this case it happens to be a template within a template, /signin from the signin state, and /child from the specifics pasted into signin
In the states, you are giving names and properties to the URLs that will be part of your app: like, if someone goes to index.html/signin, the 'signin' state is summoned.
The template within a template might be how you'd get the box variability that you're looking for. In box.html you will have another <div ui-view></div> and that's where the varying thing described in 'signin.child' gets put in.
box.html is also the place you put your specially created html tags, the ones you will make with the directive. I'll talk through those below.
The parent template, 'signin', talks about BoxController. Let's say you built that in box.js. It would look like this:
angular.module('yourApp.box', [])
.controller('BoxController', function ($scope) {
$scope.definedProperty = 'reRegPass',
$scope.arrayOfObjects = [{prop: 'red'},{prop: 'blue'}]
})
.directive('specificBox', function(){
return {
restrict: 'EA',
templateUrl: 'app/box/box-guts.html',
replace: true,
scope: {
source: '='
},
link: function(scope, element, attribute) {
element.on('click', function() {
alert('Functionality in specificBox is working');
}
}
}
});
The first line is again summoning angular module, and then naming it: note we already listed this, 'yourApp.box', as a requirement in app.js. Then we have the controller-naming function, and the appearance of specific properties in their $scope.
The directive is taking the more useful of the two forms a directive can take. They can either return functions (with signatures like the one in .link), or they can return objects that describe new HTML entities. This one is named specificBox, which Angular will translate into <specific-box></specific-box>. This object (technically called a 'directive definition object') can be translated into HTML tags because of the restrict property... 'E' is for element. 'A' is for attribute. I don't know how something that can be an element could also be an attribute, but having both options works, so I'm going with it. (Maybe for your various signin boxes you want a directive that has just 'A' to make new types of attributes.)
You put these specifically-crafted tags into box.html, and everything in box-guts.html will be between those tags... In fact, replacing those tags because of the replace: true.
The .link property is where you would put a function that would do something interesting to that element; turn it red if clicked, whatever. I have an alert example. Uses jQuery Lite for events.
The .scope property is odd. I believe if left off, the specific-box stuff would have the same controller as box (ie, BoxController), with box's scope and dependencies. But here, instead, we're making an isolate scope. box-guts will not depend on anything or have access to anything built before. How will it get interesting changeable data, then..? Every property in the scope object becomes an attribute on specific-box.
The '=' means that you will pass it objects from somewhere, and they will have 2-way data binding (changes made in box-guts will be reflected in the object collection in your app). Other options are '#', one-way data binding, which means you're passing box-guts a string or something that, if it changes in the DOM, you don't care to reflect in your app; or '&' to give it a function from a controller somewhere else in your app.
So ultimately, in box.html, you will have something like
<div class="box-holder">
<div ui-view></div>
<specific-box source="thing" ng-repeat="thing in arrayOfObjects"></specific-box>
</div>
Like I said above, ask questions and maybe we can sort this out.

Two way binding of form input directive with isolated scope

I am leaning AngularJS, specifically working on learning directives. Would like to have a form input directive that I can re-use on all my forms to encapsulate all the boiler plate markup. Though I am having trouble getting two way binding to work in my directive. It is using an isolated scope with its own internal property to store the value of the input field. I've setup a watch on that internal property that is correctly pushing the value from within the isolate scope up to the controllers scope. What I am trying to figure out is how to take an initial value from the controllers scope and set it as the initial value in my directive.
Plunker Link: http://embed.plnkr.co/TbVB0q9DHhBCVLQ4U64W/script.js
Typing in the first input box changes the controller scopes property, but not the directives value. Typing in the second input box changes the directive and the controller property.
I know this is possible using an attribute to pass the initial value. However I am hoping to be able to extract the value from the controller scopes property via the ngModel reference in my directive.
EDIT AFTER ANSWER:
For those not sure about why you want to go through the effort of learning directives. Especially when Angular is so powerful even without using directive. This is one good reason why.
Input fields on my form without directive:
<div class="form-group">
<label for="firstName" class="col-sm-6 col-md-4 control-label">First Name</label>
<div class="col-sm-6 col-md-8" ng-class="{'has-error': userForm.firstName.$invalid}">
<input type="text" id="firstName" name="firstName" placeholder="First Name" ng-model="muState.currentUser.firstName" class="form-control" required
popover="Cannot be blank" popover-trigger="{{{true: 'mouseenter', false: 'never'}[userForm.firstName.$invalid]}}" />
</div>
</div>
After using my directive:
<ws-form-input input-name="firstName" input-label="First Name" input-placeholder="First Name"
ng-model="muState.currentUser.firstName"
required input-error="Cannot be blank"></ws-form-input>
Go through the effort. You'll be trading the headaches of learning versus a maintenance nightmare later.
I think you can simplify your directive altogether by using the isolated scope's '=' attribute notation.
Something like this:
JAVASCRIPT
app.directive('inputDirective',function(){
return {
restrict: 'E',
replace: true,
scope: {ngModel: '='},
templateUrl: "directiveTemplate.html",
require: '^form',
link: function(scope, elem, attrs, ctrl){
scope.form = ctrl;
scope.required = false;
// If attribute required exists
if (attrs.required !== undefined) {
// ng-required takes a boolean which is read from this scope variable
scope.required = true;
}
}
};
});
HTML DIRECTIVE
<div>
<input type="text" id="directiveInput"
ng-model="ngModel" class="form-control" ng-required="required"/>
<br/>
Isolated Scope value of the input box: {{ngModel}}
</div>
See UPDATED PLUNKER
You have to implement ngModel.$render, see ref here.
The following code in your directive will suffice:
scope.ngModel.$render = function() {
scope.inputBinding = scope.ngModel.$viewValue;
};

AngularJS: 'Template for directive must have exactly one root element' when using 'th' tag in directive template

I'm trying to implement custom sortBy directive in order to make columns in html table sortable.
HTML:
<thead>
<tr>
<sort-by-directive
ng-repeat="header in headers"
onsort="onSort"
sortdir="filterCriteria.sortDir"
sortedby="filterCriteria.sortedBy"
sortvalue="{{ header.value }}">{{ header.title }}
</sort-by-directive>
</tr>
</thead>
JS:
angular.module('mainApp.directives').directive('sortByDirective', function () {
return {
templateUrl: 'SortHeaderTemplate',
restrict: 'E',
transclude: true,
replace: true,
scope: {
sortdir: '=',
sortedby: '=',
sortvalue: '#',
onsort: '='
},
link: function (scope, element, attrs) {
scope.sort = function () {
if (scope.sortedby == scope.sortvalue)
scope.sortdir = scope.sortdir == 'asc' ? 'desc' : 'asc';
else {
scope.sortedby = scope.sortvalue;
scope.sortdir = 'asc';
}
scope.onsort(scope.sortedby, scope.sortdir);
}
}
};
});
Directive Template:
<script id="SortHeaderTemplate" type="text/ng-template">
<th ng-click="sort(sortvalue)">
<span ng-transclude=""></span>
<span ng-show="sortedby == sortvalue">
<i ng-class="{true: 'sorting_asc', false: 'sorting_desc'}[sortdir == 'asc']"></i>
</span>
<span ng-show="sortedby != sortvalue">
<i ng-class="{true: 'sorting', false: 'sorting'}[sortdir == 'asc']"></i>
</span>
</th>
</script>
So when I use th as root tag of directive template I retrieve an error:
Error: [$compile:tplrt] Template for directive 'sortByDirective' must have exactly one root element. SortHeaderTemplate
but when I change th to a or span tags everything works fine.
What am I doing wrong?
I've encountered oddities like that with directive and table elements. See this issue for example. Try wrapping your template with div tag or use replace:false.
This isn't your case, but I had this very same issue because my code had html comments before and after the template markup, like so:
<!-- Foo Widget -->
<div class="foo-widget">[...]</div>
<!-- end:: Foo Widget -->
I got rid of the comments and voilá - problem solved.
I expect that the <th> is getting melted away at some intermediate point when it is evaluated outside the context of a <tr> (put that template into some random part of your webpage to see the <th> disappear).
In your position, I would use a <div> in the template, change sort-by-directive to an 'A' type directive, and use a <th sort-by-directive>...</th> as before, without replace: true.
This error can be also caused by the fact that you need to have a wrapping element for all your tags in the directive's template. Your directive's template can't be only:
<nav></nav>
<div></div>
It must be:
<div>
<nav></nav>
<div></div>
</div>
I got this error when I used the template property of the directive definition when I should've been using templateUrl if that helps anyone.
I know this is old, but there is another solution.
I encountered this issue also, and tried all the above solutions with no luck.
turns out, for some weird reason, that this error is thrown also in case there is a typo in the 'templateUrl' - if angular can't find the html file by the given path - you get the same 'must have exactly one root element' error.
so - fixing the templateUrl fixed the error for me.
hope this helps anyone in the future.
As stated by others: this is because your browser ignores the TH before it gets placed inside the table. My prefered way to fix this is to change the directive to an attribute directive and add it to a TH in the table.
Directive looks like this:
.directive('sortByDirective', function () {
return {
templateUrl: 'SortHeaderTemplate',
restrict: 'A',
transclude: true,
replace: false,
scope: {
sortdir: '=',
sortedby: '=',
sortvalue: '#',
onsort: '='
},
link: function (scope, element, attrs) {
scope.sort = function () {
if (scope.sortedby == scope.sortvalue)
scope.sortdir = scope.sortdir == 'asc' ? 'desc' : 'asc';
else {
scope.sortedby = scope.sortvalue;
scope.sortdir = 'asc';
}
scope.onsort(scope.sortedby, scope.sortdir);
}
}
};
});
Setting it on your page looks like this:
<th sort-by-directive
ng-repeat="header in headers"
onsort="onSort"
sortdir="filterCriteria.sortDir"
sortedby="filterCriteria.sortedBy"
sortvalue="{{ header.value }}">{{ header.title }}
</th>
I have encountered the issue couple of times and most of the times it could be that you are not wrapping your elements under one element like
<div>
<div... </div>
</div>
But there was one occasion where you get this error when the template path is not correct. So please check if you are referring to the template correctly.
A more specific case of Aaron's answer is when you think that you have the correct path to the template while you actually don't: as it's stated in the documentation for the templateUrl
The URL is relative to our index.html file
In my case, I have placed my templates directory one level below index.html. When I was setting my property to templateUrl: '../templates/some-form.html', the path was relative to the script but not to index.html, resulting to the same error.
i encounter the following error:
Error: [$compile:tplrt] http://errors.angularjs.org/1.2.6/$compile/tplrt?p0=stockWidget&p1=stock.html.
I get around by removing commement at very top of template file.
replace is deprecated with angularjs 1.3 forward, the next release will remove it completely, it's better not to use replace key.
Which version of angular are you using ?
There was a bug for something looking like your problem that was fixed in 1.2.13 1.3 Beta 1 commit link
https://github.com/angular/angular.js/issues/1459
I know it's very old answer and question but I encountered this error and fix it by put my comment inside a div tag.
before:
<!--You commented code-->
<div>
</div>
after:
<div>
<!--You commented code-->
</div>
Adding to Biljana's answer, when you think that you have the correct path to the template while you actually don't, specifically upper/lowercase differences in dos/mac/unix systems.
My local path:
templateUrl: 'ng/ImageDnd/dropzone.tpl.html',
my real path:
ng/ImageDnD/dropzone.tpl.html
notice the D at the end of AnnotationsDnd.
My Macbook does not complain, but got an error when deploying in Linux.

Categories