I am creating a custom directive for my Angular app that has a basic functionality. Along with the directive, I also want to add an attribute that works like an additional settings option so I can add extended functionality if an attribute is mentioned.
I want to do something like this:
<div use-date-picker this-ngmodel="formData.dt" today></div>
In the above custom directive, I want the datepicker input field to have today's date only when the today attribute is added. Now, I dont know how to define the function in that directive so it adds the default todays date only when 'today' attribute is added to the DIV that is calling the directive. I tried this but it doesnt work:
app.directive('useDatePicker', function() {
return {
restrict: 'A',
replace:true,
scope: {
thisNgmodel: '='
},
template: '<div class="input-group">' +
'<input type="text" class="form-control" readonly="readonly" name="dt" ng-model="thisNgmodel" datepicker-popup="{{format}}" is-open="datepickers.dt" datepicker-options="dateOptions" ng-required="true" close-text="Close" />' +
'</div> ',
link: function(scope, element, attr) {
console.log(scope.thisNgmodel);
console.debug(scope);
// Here I am trying to add the default Today's date if today attribute is added
if (element.attr('today')) {
$scope.today();
}
},
controller: function($scope) {
//DatePicker
$scope.today = function() {
$scope.thisNgmodel = new Date();
};
// ....... etc.. etc.. with other controller settings .......
}
};
});
Can I add extra functions to the directive template if an attribute is mentioned? What have I done wrong in the above code?
controller attribute of directive is not same as module.controller. It's main purpose is communication with other directive's api... quote from angularjs docs
Best Practice: use controller when you want to expose an API to other
directives. Otherwise use link
you can just set model's value to new date at link function...
if (element.attr('today')) {
scope.thisNgmodel = new Date();
}
Not sure if it's a typo on your end when posting your question, but in the linker function you 're passing in scope and then targeting $scope.
link: function(scope, element, attr) {
console.log(scope.thisNgmodel);
console.debug(scope);
// Here I am trying to add the default Today's date if today attribute is added
if (element.attr('today')) {
// $scope.today(); -- $scope doesn't live here.
scope.today();
}
},
Edit:
Try this out:
attr.$observe('today', scope.today);
This should run the scope.today function if today has been assigned in the markup. Lemme know if this takes care of it.
Related
I have this directive view here in my code:
<div class="busy-indicator angular-animate" ng-show="busy"></div>
<div class="breadcrumblist" ng-class="atTopLevel ? ['at-top-level'] : null">
<div class="breadcrumblist-display angular-animate">
<label id="searchBox">Search</label><input class="c-context-menu-container t-menu-background c-context-menu-text" type="text" autocomplete="off" id="IdSearch" ng-model = "searchText.Name">
<div class="breadcrumblist-parents">
<div ng-show="::parents && parents.length >= 1"
ng-repeat="parentLink in parents"
class="breadcrumblist-parent-link t-breadcrumb--parent-bgcolor t-border--bottom-grey48"
ng-click="navUpToParent(parentLink)"
ng-class="{'selected': isSelected(parentLink.object), 'draggable': isDraggable(parentLink.object)}"
data-index="{{$index}}">
</div>
But, the searchBox is appearing for all places on my app but I want to make it appear just for one directive in particular. What should I do? I tought about make a scope variable to just "ng-show" this particular searchbox with a condition, but I don't know exactly how to do that, can you help me?
Until you give more information to your specific problem, here are some possibly relevant things you can do to debug your problem and find a solution
the searchBox is appearing for all places on my app but I want to make it appear just for one directive in particular
The combination of the name of the directive + its restriction might cause the directive to appear in unwanted locations.
More information on restrict if needed.
So for example, if you have a directive called 'breadcrumblistParentLink', which is restricted to C (class) - you might find it in undesired locations since you're also using this class to style some elements on your page.
If that's the case, you might find it helpful restricting directives to attributes and elements and giving some unique names.
I would also like to refer to your question
just "ng-show" this particular searchbox with a condition, but I don't know exactly how to do that
If you want a specific behavior for one instance of a directive, there are multiple ways to do that.
Most common is passing an attribute. For example, this is how you use it
<div my-awesome-directive show-searchbox="true"></div>
And this is how you'd put show on the directive scope
angular.module('myApp',[]);
angular.module('myApp').directive('myAwesomeDirective', function(){
return {
template: 'this is my template <span ng-show="showSearchBox">This is search box</span>',
restrict: 'A',
scope: {
showSearchBox: '<'
},
link: function(scope, element, attrs){
console.log(scope.showSearchBox);
}
}
})
You can play around with it here: https://plnkr.co/edit/4MdNeEafbZjq2kEFKYAl?p=preview
You can also look directive at attrs variable (attrs.showSearchBox) and spare the binding in some cases.
For example:
angular.module('myApp',[]);
angular.module('myApp').directive('myAwesomeDirective', function(){
return {
template: 'this is my template <span ng-show="showSearchBox()">This is search box</span>',
restrict: 'A',
scope: {
},
link: function(scope, element, attrs){
scope.showSearchBox = function(){
return attrs.showSearchBox;
}
}
}
})
Note I am using a function on the scope.
This function can also refer to the DOM and do something like $(element).is('[show-search-box]') if you are more comfortable with jquery - but it is highly recommended to stick to angular way of doing things.
Consider a custom directive in angularjs:
.directive('myDir', function() {
return {
restrict: 'E',
template: "..."
}
})
As far as I see the tag <my-dir></myDir> has no default style associated (or, at least, it is not a block tag). Now I want to style it and place in the right point of my page. I have 2 alternatives:
1) Use this template:
.directive('myDir', function() {
return {
restrict: 'E',
template: "<div class="layout">...</div>",
}
})
And css:
.layout {
/* bla bla bla */
}
But this introduce a new unnecessary level in the DOM three, since if I wrote something like <my-dir class="layout"></my-dir> with the proper css attached it would have worked anyway BUT I'll have to remember to add the same css class every time I use <my-dir> inside my code (and this is not DRY).
2) This led me to add style inside post-link function:
.directive('myDir', function() {
return {
restrict: 'E',
template: "...",
link: function(scope, element, attrs) {
element.addClass('layout');
}
}
})
Which strategy is better? Are there pros or cons I can't see?
UPDATE:
Using replace: true in directive definition is not an option, since it has been deprecated and when using bootstrap things like <my-dir class="visible-xs"></my-dir> may be useful.
You could solve this problem by having replace: true option in your directive, which will basically replace your directive DOM with the template which you have given from your directive.
Directive
.directive('myDir', function() {
return {
restrict: 'E',
template: "<div class="layout">...</div>",
replace: true //added replace true property
}
})
Resultant HTML
<div class="layout">..Content..</div>
Like above I shown, you could get rid off the unnecessary HTML tag by simply using replace option of directive.
Demo Plunkr
Note
As per Angular Docs replace option has been deprecated in Angular2,
but in your case you can avoid that.
But if you really wanted to go avoid the use of replace option of directive then I'd suggest you to use your directive as an attribute instead of going for an element but then you need to take <div class="layout"> outside and then your could put your directive over that like <div class="layout" my-dir>. But if you again think over it in Angular2 perspective, you are changing your component structure to old angular way using attribute/class
with this Angular directive every time my model changes, new HTML item appended to the page:
app.directive('helloWorld', function($compile) {
return {
restrict: 'AE',
replace: true,
scope:{
arrayItem: '=ngModel'
},
link: function ($scope, ele, attrs) {
$scope.$watch( 'ngModel' , function(){
ele.html('<div ng-click="sendLike({{arrayItem.data.timeline_content}})" class="timeline-item"> Hello {{arrayItem2.data.timeline_content}} </div>');
$compile(ele.contents())($scope);
});
}
};
});
And this is HTML view:
<hello-world ng-repeat="arrayItem in arr" ng-model="arrayItem"></hello-world>
But ng-click inside dynamically generated HTML doesn't work. actually recompiling of this new added section does not works.
UPDATE:
this is what i want to achieve:
in fact i want to create a chat Application. messages stored inside an Array and i have to bind that array to the HTML view. if i click on every message, i need to an alert() fired inside the controller. my controller is like this:
app.controller("timelineCtrl", function ($scope) {
$scope.arr={};
$scope.sendLike = function (id) {
alert(id);
};
.
.
.
}
in the jQuery way, i simply use DOM manipulation methods and add new tags for each message but in Angular way i have to bind that array as a ng-model or something like that.
at first glance, i realize that designing a directive should be a good idea and inside module i can access to main scope and do needed thing with that directive and i expect that changes inside that directive should projected to HTML view but it fails and things like ng-click doesn't work for dynamically created tags.
There are two ways that you could accomplish this, with or without a directive.
Let's start without a directive; we'll assume that you have an array in the controller.
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<div ng-click="sendAlert(post)">
<span class="postnumber">{{::post.lineNumber}}:</span>
<span class="message">{{::post.message}}</span>
</div>
</div>
</div>
whenever an object is added to $scope.timeline, it can be assigned a lineNumber, and we can use angular OrderBy to sort the direction in reverse lineNumber order (using -). The $scope.sendAlert(post) will send the specific post to the function. in our bindings, we use :: to indicate that these are one time bindings, i.e. not values that need to be monitored independently of the array. This can improve performance on large lists.
using a Directive, we can accomplish this in a very similar manner, by making a Directive that renders a specific post, and passing the post in as a property.
app.directive('timelinePost', function() {
return {
restrict: 'AE',
scope:{
post: '='
},
template: '<div ng-click="postCtrl.sendAlert()">
<span class="postnumber">{{::postCtrl.post.lineNumber}}:</span>
<span class="message">{{::postCtrl.post.message}}</span>
</div>',
controller: 'postController',
controllerAs: 'postCtrl',
bindToController: true
};
app.controller("postController", function(){
var self = this; //save reference to this
self.sendAlert = function(){
//the specific post is available here as self.post, due to bindToController
};
};
//usage in HTML:
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<timeline-post post='post'></timeline-post>
</div>
</div>
you could further wrap this timeline in a directive in a similar manner, if you so desired. Either of these will accomplish the same task, of looping through your data, ordering it so that the newest post is at the top, and updating whenever the array changes. In the non-directive method, the timelineCtrl handles the $scope.sendAlert function; in the directive method, it is handled by the directive controller postController.
Please note, this is a rough draft based on what you have described and the information from various posts over the last 2 days. I haven't created a dataset to iterate through to test thoroughly, but the logic here should get you started.
I am attempting to create a re-usable directive wrapper for ng-grid where I can apply the location of the ng-grid options through the use of an attribute.
Here is the skeleton of the code which gets very close to what I want:
angular.module('myApp').directive('grid', function() {
return {
restrict: 'E',
template: '<div class="gridStyle" ng-grid="someObject.gridOptions"></div>',
link: function(scope, element, attributes) {
// no code here necessary, to use the hard-coded ng-grid options
}
}
}]);
What I would like to do is supply the variable for ng-grid by using an attribute, similar to this:
<grid dataLocation="someObject.gridOptions"></grid>
I've tried using the compile and the link options with multiple methods, including reading the attributes, and then using the element.html() method to update the html to set the ng-grid attribute to "someObject.gridOptions", as well as using {{someScopeVariable}}, and setting scope.someScopeVariable to "someObject.gridOptions" in the linking function. I have verified using the chrome's html inspector that the div's attribute looks correct, but I have not been able to get the item to show up on my page.
I suspect I'm running into issues since the ng-grid is already a compiled directive. Is there any way I can pull this off? I've tried a large number of compile and linking methods with no success yet.
To access the original template's attributes, the template option of directive should be defined as a function as follows:
angular.module('myApp').directive('grid', function() {
return {
restrict: 'E',
template: function(element, attrs){
return '<div ng-grid="' + attrs.dataLocation + '"></div>';
}
};
});
Your HTML stays the same:
<grid dataLocation="someObject.gridOptions"></grid>
I made a directive that wraps freebases jquery search widget. I want the user to be able to change the search language on the fly so I'm using $observe to watch the 'lang' attribute.
The issue is the plugin reinitialized every time the attribute changes but does not end the execution of the previous initialization. When the user selects terms, I'm adding it to an array. If the lang attribute has been modified, the selected term is added for every change to the attribute.
I could just filter the array and only allow items with the same ID to appear once, but I'm wondering if there is a better solution.
See plunker here.
The directive:
directive('suggest', function() {
return {
restrict: 'E',
template: "<input type='text'>",
replace:true,
scope:{onSelect:'&'},
link: function(scope, element, attrs) {
attrs.$observe('lang', function(value) {
$(element).suggest({
lang: value
})
.bind("fb-select", function(e, info) {
console.log(info);
scope.onSelect({data:info});
scope.$apply();
});
});
}
};
});