How to set list of required attributes in AngularJS Directive? - javascript

I have created AngularJS directive that requires HTML element to:
1) be a child of the form;
2) have 'conditional-value' attribute;
To accomplish this I created directive like this:
.directive('onFocusoutDoSomething', function () {
return {
require : ['^?form', '^conditionalValue'],
restrict : 'A',
link : function(scope, element, attrs, ctrl) {
// ... do something
}
}
})
But for some reason I get this Exception:
Controller 'conditionalValue', required by directive 'onFocusoutDoSomething', can't be found!
Camel case directive name and attributes are replaces with dash cases by AngularJS, right?

If you have to create 'A' attribute type directive, it should be placed in scope{conditionalValue:'='} of directive or bindToController:{conditionalValue:'='}, not in require:{}, require is used for directive to directive communication.
If you have require in directive definition object, that directive is inheriting form parent directive mentioned in require field. for example if you have,
app.directive('tabSet',function(){
//DDO
});
tabs directive can inherit the controller properties of tabSet directive.
app.directive('tabs',function(){
return{
require:'^tabSet'
}
});

Related

Required directive controller is not present on the current DOM element

So I was wondering what actually "required directive controller is not present on the current DOM element" means
here is the link of error:https://docs.angularjs.org/error/$compile/ctreq?p0=ngModel&p1=contenteditable
You are missing the ngModel directive for your custom directive.
Try something like this:
app.directive('contenteditable', function () {
return {
restrict: 'E',
require: 'ngModel',
link: function (scope) {
// do something
}
};
});
and in your HTML file add ng-model
<contenteditable ng-model="name"></contenteditable>
From the angular.js documentation on directives
When a directive uses require, $compile will throw an error unless the specified controller is found. The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).

Add custom Directive to existing Input that already has angular directives [ng-model/ng-required]

I would like to use a standard input control that is decorated with ng-model and ng-required and then add my own custom attribute directive that provides uib-typeahead functionality to the control.
I used this link to get my directive partly working.
Add directives from directive in AngularJS
PLUNKR - The Version 2 of the directive does not work correctly with ng-model
My Directive does add typeahead functionality and that works quite well, but it is not binding the model on to the control after item is selected.
I have two version of my directive.
Version 1: is an element style directive and I have been using it successfully for a while, but it fell short when I wan't to have a bit more control over the input element, especially when I wanted to use ng-required='true' and other ng-message directives.
Version 2: is an attribute style directive, I went with this because I felt it was better to just add the typeahead functionality that I wanted to any standard HTML that can optionally use ng-required='true', ng-model etc...
While this directive is mostly working, it does not interact correctly with ng-model and I'm not sure how to get it working
angular.module(APP)
.directive('wkLocationSuggest', ['$compile', function ($compile) {
return {
restrict: 'A',
require: 'ngModel',
replace: false,
//terminal: true,
//priority: 0,
scope: {
wkApiModel: '=' // Provide access to the internal data that is returned via the API lookup
},
controller: 'LocationSuggestController',
link: function (scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
element.attr('typeahead', 'location as row.location for row in typeAhead($viewValue)');
element.attr('typeahead-wait-ms', '750');
element.attr('typeahead-on-select', 'onSelectInternal($item, $model, $label)');
element.attr('typeahead-min-length', '2');
element.attr('typeahead-focus-first', 'true');
element.removeAttr("wk-location-suggest"); //remove the location-suggest to avoid indefinite loop
element.removeAttr("data-wk-location-suggest"); //also remove the same attribute with data- prefix if it exists
// None of this is working
//// invoked when model changes from the outside
//ngModelCtrl.$render = function () {
// //scope.innerModel = ngModelCtrl.$modelValue;
//};
////// invoked when model changes from the inside
//scope.onChange = function (value) {
// ngModelCtrl.$setViewValue(scope.innerModel);
//};
scope.onSelectInternal = function ($item, $model, $label) {
// This fires, but it effects the ng-model on the first input,
// but not the input that this directive is attached too
ngModelCtrl.$setViewValue($item.location);
};
$compile(element)(scope);
}
};
}]);
These two images demonstrate part of the problem, may be better to test for yourself using PLUNKR above
I initially tried to dynamically add validators to your wk-location-suggest-new directive by implementing blur on the input element in combination with ngModel's $setValidity method; but don't know what exactly was preventing the event from firing.
Therefore, I turned to the other directive wk-location-suggest-old and tweaked it a bit to fit in both desired behaviors.
There, I noticed that you were missing a couple of things:
First of all, in order for a form element to glue with the form itself (wkProfileCompany in your case), and to work with ng-model, the element (in the directive template) needs a name.
Secondly, ng-required (or required) would work with the form only if it is added as an attribute to the element in the directive template, not the directive which compiles to the template containing the element.
Directive Definition
As you may notice, I've passed two properties from the outer scope to the directive's inner scope, namely:
the name of the input element,
and an isRequired flag as to specify whether the input is required or not.
.
.directive('wkLocationSuggestOld', [function () {
return {
restrict: 'E',
require: '?ngModel',
scope: {
name: '#', // <==
isRequired: '=' // <==
},
template: '<input name="{{name}}" type="text" class="{{innerClass}}" ng-model="innerModel"'
+ ' ng-change="onChange()" uib-typeahead="location as row.location for row in typeAhead($viewValue)" '
+ ' typeahead-wait-ms="750" typeahead-on-select="onSelectInternal($item, $model, $label)" '
+ ' typeahead-min-length="2" typeahead-focus-first="true" '
+ ' ng-required="isRequired">', // <== added ng-required here
controller: 'LocationSuggestController',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
...
}])
HTML
Finally, you can use the tweaked directive in your HTML as such:
<wk-location-suggest-old class="form-control" type="text" name="location2" ng-model="location2" is-required="true"></wk-location-suggest-old>
Plunker
Update
One of the possible reasons for ng-model not correctly binding in the wk-location-suggest-new directive to a provided value (i.e. location3) is that you are replacing the whole DOM element with a new custom DOM element which is compiled with the isolated scope of the directive itself.
Since the directive wk-location-suggest-new has an isolate scope, the scope is totally unaware of location3, because location3 (and all the other location values) are defined in the scope of MainCtrl and NOT the scope of the directive itself; therefore, you'll end up binding the input's value to an undefined property.
link: function (scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
...
$compile(element)(scope); // <== here
You need to update your model in setTimout() like below as you have an isolated scope in the directive.
setTimeout(function () {
scope.$apply(function () {
scope.location3 = 'Your selected value'
});
}, 2000);
Alternatively you can also utilize $timeout service to achieve the same result.

angularjs directive: how communicate between link and controller?

I have a directive whose 'config' attribute value I need to access inside my directive controller.
Since the controller constructor get executed first,communication from controller to link is possible but not vice versa.
What should be the best way to achieve this?
I have considered the following approaches
1)Add the variable to scope-
That would in my opinion pollute the scope,making the variable accessible every where else where the scope is being shared.
2)Use $broadcast
Again the same issue as above
3) Pass a callback function on controller's this and call it from the link function with config as its argument
4)Pass the value through a service- In my case I have multiple such directives that would need to pass date through this service
Or is there some better approach that I am missing out for doing this?
module.directive('myDirective',function(){
return{
restrict:'E',
templateUrl:'path/to/html',
link:function(scope,iElement,iAttrs,controller){
var config=iAttrs.config;
//How to access this value inside the directive controller?
},
controller:function($scope){
//the directive attribute 'config' is required here for some larger computations which are not
//manipulating the DOM and hence should be seperated from the link function
})
There you can use isolated scope concept where you create isolated scope inside your controller & that would not be prototypically inherited from its parent scope. For that you need to use scope: { ... } inside your directive option. There are three options to pass scope value inside a directive through attribute
# : One way binding
= : Two way binding
& : Expression
In your case first two cases would be fine that are depends which one you need to use. If you just want to pass the value of scope variable to the directive in that case you could use 1st approach which would be # one way binding.
If you want to update the variable in both directive as well as controller from where it come i.e. nothing but two way binding, then you need to use =
I think = suits in your case so you should go for =
Markup
<my-directive config="config"></my-directive>
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '='
},
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope) {
console.log($scope.config); //here also it would be available inisde scope
//you could put a watch to detect a changes on config
}
}
});
Demo Plunkr
Update
As config value has been provide from the attribute with expression like {{}} so we could get those changes inside controller by putting [**$observe**][2] on $attrs. For that you need to inject $attrs dependency on your controller that will give you all the attributes collection which are available on directive element. And on the same $attrs object we gonna put $observe which work same as that of $watch which does dirty checking & if value gets change it fires that watch.
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope,$attrs) {
//you could put a watch to detect a changes on config
$attrs.$observe('config', function(newV){
console.log(newV);
})
}
}
});
Updated Demo

AngularJS directive parameter "ctrls"

I see a link function in a directive in Angular like so:
link: function (scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0],
invalidInputController = ctrls[1];
// ...
}
Can someone help me understand where the controllers in the ctrl parameter are coming from?
Edit: the directive has an angular require property specifying two other directives by name like so:
require: ['ngModel', '?numberFormatterPreventInvalidInput'],
I suspect they come from there.
Typically they come from the require part of a directive, which is either a string or an array of controllers.
['^something', '^another']
Within link they are accessed by ctrl[0] and ctrl[1].
Also from the documentation:
The basic difference (between controller and link) is that controller can expose an API, and link
functions can interact with controllers using require.
Best Practice: use controller when you want to expose an API to other
directives. Otherwise use link.
if in your directive is written
require:["ngModel","^directiveTwo"]
than ngModel , directiveTwo are names of directives and directiveTwo has to be a parent directive
link: function (scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0],
controllerOfDirectiveTwo = ctrls[1];
// ...
}
ctrls the last parameter isthe array of the controller of the directives defined by require so for ngModel this :https://docs.angularjs.org/api/ng/type/ngModel.NgModelController for your directives defined by the controller: in your directive

Angular Directive to Directive call

If you have a directive that you're using multiple times on a page how can 1 directive communicate with another?
I'm trying to chain directives together in a parent child relationship. When directive A is clicked i want to filter Directive B to only have the children of the selected item in Directive A. In this case there may be infinite number of directives and relationships on the page.
Normally i would have Directive A call a filter method on each of it's children, and each child calls it's child to continue filtering down the hierarchy.
But i can't figure out if calling methods from 1 directive to another is possibe.
Thanks
It sounds like you are looking for a directive controller. You can use the require: parameter of a directive to pull in another directive's controller. It looks like this:
app.directive('foo', function() {
return {
restrict: 'A',
controller: function() {
this.qux = function() {
console.log("I'm from foo!");
};
},
link: function(scope, element, attrs) {
}
};
});
app.directive('bar', function() {
return {
restrict: 'A',
require: '^foo',
link: function(scope, element, attrs, foo) {
foo.qux();
}
};
});
From the angular docs, here are the symbols you can use with require and what they do.
(no prefix) - Locate the required controller on the current element.
? - Attempt to locate the required controller, or return null if not found.
^ - Locate the required controller by searching the element's parents.
?^ - Attempt to locate the required controller by searching the element's parents, or return null if not found.
Here's a jsbin of my example. http://jsbin.com/aLikEF/1/edit
Another option that may work for what you need is to have a service that each directive sets up a watch on and can manipulate. For example, directive1 may watch a property in the service and respond to changes and also setup a button that can change that property. Then, directive2 can also watch and change the service, and they will respond to one another however you set that up. If you need a jsbin of that also, just let me know.
I hope this helps!
You could try putting all of the data into a service that the directives can each reference.
Something like:
app.factory('selectedStuffService', function(){
var allItems = [];
var selectedItems = [];
function addSelectedItem(item){
selectedItems.push(item);
}
return {
allItems: allItems,
selectedItems: selectedItems,
addSelectedItem: addSelectedItem
}
}
Interactions in directive A change the values in the selectedItems array and directive B can bind to it. You can easily add other methods to the service to filter/manipulate the items as needed and any directive that uses the service should be able to update based on changes made by other directives.

Categories

Resources