I'm having two directives with same functionality like below.
angular.module('ui.directives', [])
.directive('uiFoo',
function() {
return {
restrict: 'EAC',
link: function($scope, element, attrs) {
//to do functionality
element.append("test content");
}
};
})
.directive('uiFoo1',
function() {
return {
restrict: 'EAC',
link: function($scope, element, attrs) {
//to do functionality
element.append("test content");
}
};
});
They both contain the same working like here it is appending "test content" as text to that element.
Is there any chance that instead making this two directive. Can i write two names for one directive/ I can use same functionality in with optimize code.
Here I'm writing same code for without any meaning.
Instead of writing directive two times, is there any optimize way.
I'm new in AngularJS kindly help me.
Thanks in advance!
The simpliest way would be to extract your directive into a JS object and use this instead.
Alternativly you can provide the directive object with a angular provider, if you want to stay in the angular context.
But, why do you want to have two directives with the exact same functionality in the first place?
Directives can used as often as you want, so this seems like a design flaw to me.
var myDirective = [function(){
restrict: 'EAC',
link: function($scope, element, attrs) {
//to do functionality
element.append("test content");
}
}];
angular.module('ui.directives', [])
.directive('uiFoo', myDirective)
.directive('uiFoo1', myDirective);
Related
//main controller
angular.module('myApp')
.controller('mainCtrl', function ($scope){
$scope.loadResults = function (){
console.log($scope.searchFilter);
};
});
// directive
angular.module('myApp')
.directive('customSearch', function () {
return {
scope: {
searchModel: '=ngModel',
searchChange: '&ngChange',
},
require: 'ngModel',
template: '<input type="text" ng-model="searchModel" ng-change="searchChange()"/>',
restrict: 'E'
};
});
// html
<custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search>
Here is a simplified directive to illustrate. When I type into the input, I expect the console.log in loadResults to log out exactly what I have already typed. It actually logs one character behind because loadResults is running just before the searchFilter var in the main controller is receiving the new value from the directive. Logging inside the directive however, everything works as expected. Why is this happening?
My Solution
After getting an understanding of what was happening with ngChange in my simple example, I realized my actual problem was complicated a bit more by the fact that the ngModel I am actually passing in is an object, whose properties i am changing, and also that I am using form validation with this directive as one of the inputs. I found that using $timeout and $eval inside the directive solved all of my problems:
//main controller
angular.module('myApp')
.controller('mainCtrl', function ($scope){
$scope.loadResults = function (){
console.log($scope.searchFilter);
};
});
// directive
angular.module('myApp')
.directive('customSearch', function ($timeout) {
return {
scope: {
searchModel: '=ngModel'
},
require: 'ngModel',
template: '<input type="text" ng-model="searchModel.subProp" ng-change="valueChange()"/>',
restrict: 'E',
link: function ($scope, $element, $attrs, ngModel)
{
$scope.valueChange = function()
{
$timeout(function()
{
if ($attrs.ngChange) $scope.$parent.$eval($attrs.ngChange);
}, 0);
};
}
};
});
// html
<custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search>
The reason for the behavior, as rightly pointed out in another answer, is because the two-way binding hasn't had a chance to change the outer searchFilter by the time searchChange(), and consequently, loadResults() was invoked.
The solution, however, is very hacky for two reasons.
One, the caller (the user of the directive), should not need to know about these workarounds with $timeout. If nothing else, the $timeout should have been done in the directive rather than in the View controller.
And two - a mistake also made by the OP - is that using ng-model comes with other "expectations" by users of such directives. Having ng-model means that other directives, like validators, parsers, formatters and view-change-listeners (like ng-change) could be used alongside it. To support it properly, one needs to require: "ngModel", rather than bind to its expression via scope: {}. Otherwise, things would not work as expected.
Here's how it's done - for another example, see the official documentation for creating a custom input control.
scope: true, // could also be {}, but I would avoid scope: false here
template: '<input ng-model="innerModel" ng-change="onChange()">',
require: "ngModel",
link: function(scope, element, attrs, ctrls){
var ngModel = ctrls; // ngModelController
// from model -> view
ngModel.$render = function(){
scope.innerModel = ngModel.$viewValue;
}
// from view -> model
scope.onChange = function(){
ngModel.$setViewValue(scope.innerModel);
}
}
Then, ng-change just automatically works, and so do other directives that support ngModel, like ng-required.
You answered your own question in the title! '=' is watched while '&' is not
Somewhere outside angular:
input view value changes
next digest cycle:
ng-model value changes and fires ng-change()
ng-change adds a $viewChangeListener and is called this same cycle.
See:
ngModel.js#L714 and ngChange.js implementation.
At that time $scope.searchFilter hasn't been updated. Console.log's old value
next digest cycle:
searchFilter is updated by data binding.
UPDATE: Only as a POC that you need 1 extra cycle for the value to propagate you can do the following. See the other anwser (#NewDev for a cleaner approach).
.controller('mainCtrl', function ($scope, $timeout){
$scope.loadResults = function (){
$timeout(function(){
console.log($scope.searchFilter);
});
};
});
I am quite familiar with CanJS, and kind of like the idea that you can instantiate a custom web widget on an HTML element, and now that we have an object, we can send messages to it (invoke a method on it):
lightbox.popUp();
or
reviewStars.setStars(3.5);
How could that be done in AngularJS? After you make a directive and set it on an HTML element or use the directive as an HTML element, how do you do something like above, as in OOP, or how Smalltalk would do it -- sending messages to a particular object?
I could think of a way, such as:
<review-stars api="ctrl.reviewStarAPI"></review-stars>
and then for the reviewStar directive, do this:
scope: { api: "=" }
link: function(scope, elem, attrs) {
// first define some functions
scope.setStars = function(n) { ... };
// and then set it to the api object:
scope.api.setStars = scope.setStars();
}
and then in the controller, do
vm.reviewStarAPI.setStars(3.5);
but this is a bit messy, and somewhat ad hoc. And it always need to have a controller... although, I suppose we can use 1 controller and as the main program and instantiate a bunch of objects and then invoke methods on them this way.
What is/are ways to accomplish this besides the method above?
A modular approach to this would be to create a directive called reviewStars. The directive should have a parameter that indicates the star rating.
For example:
<review-stars rating="3.5">
You would create using something like the following:
angular.module('myAngularModule', [])
.directive('reviewStars', function() {
return {
restrict: 'E',
scope: {},
bindToController: {
rating: '#'
},
link: function(scope, elem, attrs) {
// instantiate the lightbox code here
},
controller: function () {
var vm = this;
// controller code goes here (e.g. the onClick event handler)
},
controllerAs: 'ctrl',
templateUrl: 'review-stars.html' // the HTML code goes in this file
};
});
Check out Rangle's ng-course (https://github.com/rangle/ngcourse) or the Angular docs (docs.angularjs.org) for more on directives.
I'm receiving the error: Multiple directives [gridsection, gridsection] asking for templateon : <div gridsection=""> with this code.
I don't see how i'm using nested directives or what is causing this.
html page
<div gridsection ></div>
directive
angular.module('web').directive('gridsection', function() {
return {
restrict: 'A',
replace: false,
scope: {
patient: "=patient"
},
templateUrl: 'directive/section.html',
link: function(scope, element, attrs, fn) {
}
};
});
directive/section.html
<div>
here?
</div>
It seems like you are declaring the gridsection multiple times in your angular code.
I have seen this before when I have a copy of a directive script file in a folder.
i.e. my file structure was
* myDirective.js
* myDirective - copy.js
So essentially I had two directives with the same name.
Doh!
Note originally posted this as a comment but created as an answer in response to comment from #jayjayjay
For posterity, I was getting this exception because I was trying to create a directive named pager and that was colliding with Bootstrap's pager.
Make sure you didn't include the script tag twice.
I had this issue but only declared the directive once in the markup, turns out it was because I included the script twice.
Note: I saw this in one of the comments for another answer and posted it as an answer for easier access/to prevent it getting lost.
I was getting this error for a reason not specified in other answers.
I was using declaration for xyz directive as <xyz xyz="xyz"></xyz>
my definition was:
angular.module('app')
.directive('xyz', function () {
return {
templateUrl: '..../xyz.html',
restrict: 'EA',
scope: {
xyz: '='
},
link: function (scope, element, attrs) {
}
};
});
The problem here is that I allowed directive to be used as element and attribute. so <xyz xyz="xyz"></xyz> contained both the declaration which was causing the issue.
Solution is to either restrict the directive to be used as Element only restrict: 'E' OR change the name of the directive to something like xyzView and use it like <xyz-view xyz="xyz"></xyz-view>.
I want to create an angular directive inside of a link function, however; the directive created is not able to be compiled.
See this JSFiddle: http://jsfiddle.net/v47uvsj5/5/
Uncommenting this directive in the global space works as expected.
app.directive('test', function () {
return {
templateUrl: 'myform', // wraps script tag with id 'myform'
restrict: 'E',
require: "^mydir",
replace: true,
scope: {
},
link: function (scope, element, attrs, mydirCtrl) {
scope.remove = function () {
element.remove();
mydirCtrl.remove();
}
}
}
});
But the exact same code inside the link function fails.
The reason I want to do this is because I want the user (who is going to be myself) to be able to provide only a script tag's id via an id attribute to my main directive which will in turn create a 'wrapper' directive with a 'remove' method. This way, in the script tag, all one needs to do is implement the 'remove'.
Check out this fiddle:
http://jsfiddle.net/v47uvsj5/8/
What I did in this fiddle was daisy chain your directives, which is the correct thing to do. When your app runs, it does a binding of each of your directive and builds your html as it's being compiled, then it links events to it. Links and compilation happen after binding all directives to the DOM.
So <test></test> becomes <div></div> if you give it a template. If there is no template, nothing really builds your directive against the DOM, it just becomes empty, but you can still run a jquery script if you want.
Think of it like this, when your app loads up, it registers all the directives to be binded with the associated templates. Afterwards, the app then "compiles" those directives by binding any kind of events to the newly established DOM. At this point, if no directives are registered during app load, the compile function ignores it. In your case, you tried to bind the 'test' directive after the app load, and during the compilation.
This mechanism is analogous to how jquery's "on" works. When you do a "click" event on an already loaded DOM element, this fires up. But when you load html AFTER the DOM is finished, nothing works unless you use "on".
To be fair, the developers of angular did mention how there's a steep learning curve for handling directions, and will be revised to make it much easier in 2.0. You can read about it in this blog here: Angular-2.0
Anyways,
This is how your html should look like:
<mydir><test></test></mydir>
and this is how you daisy chain:
var app = angular.module('app', []);
app.directive('mydir', function ($compile, $templateCache) {
return {
template: '',
restrict: 'E',
controller: function () {
console.log("got it!");
}
}
}).directive('test', function () {
return {
templateUrl: 'myform',
restrict: 'E',
require: "^mydir",
replace: true,
scope: {
},
link: function (scope, element, attrs, mydirCtrl) {
scope.remove = function () {
element.remove();
mydirCtrl.remove();
}
}
}
});
Using AngularJS 1.0.8, I'm trying to create some reusable directives to create a situation where a web developer can code a single "top-level" directive with a number of attributes, and this directive, in turn, has a template containing other directives, which themselves might contain other directives etc.
The problem I'm having is making the "inner" templates aware of the top level attributes. I thought this would be a universal problem, but it didn't look, from my research, that anyone else was asking this.
I created this Plunker to show the problem:
<!DOCTYPE html>
<html ng-app="outerInnerDirectivesApp">
<head>
<title>Outer/Inner Directives</title>
</head>
<body>
<div>Single level directive follows:</div>
<single-level-directive single-level-id="single123"></single-level-directive>
<div>Outer/inner directive follows (Expecting "outer123"):</div>
<outer-directive outer-id="outer123"></outer-directive>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script src="app.js"></script>
<script src="directives.js"></script>
</body>
</html>
In the Plunker,
single-level-directive works and is, I think, a standard way to display data.
outer-directive and inner-directive aren't working.
What I expected to happen with these was
(i) outerDirective compiles/links to produce the html
<inner-directive inner-id="outer123"></inner-directive>
and then
(ii) innerDirective compiles/links to produce html
<div>outer123</div>
But at step (ii) I get
<inner-directive inner-id="" class="ng-isolate-scope ng-scope">
<div class="ng-binding"></div>
</inner-directive>
so an empty div is generated by innerDirective.
In fact, if I change outer-template.html to be
<div>{{outerId}}<div>
then the value displays correctly, so it looks like scope.outerId is available at the correct point, but Angular isn't happy about me trying to use it in the way I am.
Is this a reasonable thing to expect Angular to do? If so, what am I missing? If not, then what do you think would be a sensible alternative way to build up more complex screens from simple sets of directives?
If you are going to design directives with isolated scope, I would suggest using the isolated scope to define the type of attribute you want to use:
outerInnerApp.directive("outerDirective", function() {
return {
restrict: "E",
scope: {
outerId: '#'
},
link: function(scope, element, attrs) {
},
templateUrl: "outer-template.html"
};
});
outerInnerApp.directive("innerDirective", function() {
return {
restrict: "E",
scope: {
innerId: '='
},
link: function(scope, element, attrs) {
},
templateUrl: "inner-template.html"
};
});
Here is a working plunker.
Your outer directive is using the value that is defined in the attribute. So, to pass the value into the isolated scope, we can use #. The inner scope is then binding a variable through. So, we can use = to set up a bound attribute.
Had some more thoughts about this. Having used AngularJS a bit more, I'm not sure that I want to bind to the scope (using the "="). In fact, I can get the original Plunkr to work by making these changes:
outerInnerApp.directive("outerDirective", function() {
return {
restrict: "E",
scope: {
//add outerId here
outerId: "#"
},
link: function(scope, element, attrs) {
//remove scope assignment here
//scope.outerId = attrs.outerId;
},
templateUrl: "outer-template.html"
};
});
outerInnerApp.directive("innerDirective", function() {
return {
restrict: "E",
scope: {
//add innerId here
innerId: "#"
},
link: function(scope, element, attrs) {
//remove scope assignment here
//scope.innerId = attrs.innerId;
},
templateUrl: "inner-template.html"
};
});
What I don't understand at the moment is why there is a different between, say,
innerId:"#"
and setting the value of scope in the link function
link: function(scope, element, attrs) {
scope.innerId = attrs.innerId;
}
When I find out why it behaves differently I'll post back.