I have a pair of directives, one lives within the other and requires it using require: '^parentTag'. The two directives also declare their own controllers. Inside of the parent directive I can access its controller as the fourth argument to link: function(scope, el, attrs, ctrl) but in the child this fourth argument is either the required controller or an array of required controllers and the directives controller is no longer accessible.
I've tried to require the controller using the name used in controllerAs 'vm' and also by using the string name for the controller. Neither gets the controller back into that fourth argument.
The controller is accessible from the scope as scope.vm but I'd like to access it via that fourth argument if possible.
This Plunkr shows that the ChildController is not in the fourth argument and only on the scope but the ParentController is accessible as the fourth argument.
http://plnkr.co/edit/2WmB6Bqgc8MEG9JWz4wt
You could require the same directive by its name (never seen anyone actually do it):
function ChildTag() {
return {
restrict: 'AE',
require: ['^parentTag', 'childTag'],
scope: {},
template: '<div class="child-tag">Child</div>',
link: link,
controller: ChildController,
controllerAs: 'vm',
bindToController: true
}
function link(scope, element, attrs, ctrls, transclude) {
// ctrls[1] is ChildController
console.log('Child vm not found in ctrls', ctrls);
console.log('Child vm found on scope', scope.vm);
}
}
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);
});
};
});
What does the ^ mean under require in this angular directive?
I found this snippet and trying to figure out what it's saying.
.directive('accordionGroupHeading', function() {
return {
restrict: 'EA',
transclude: true,
template: '',
replace: true,
require: '^accordingGroup',
link: function(scope, element, attr, accessibleAccordionGroupCtrl, transclude) {
accessibleAccordionGroupCtrl.setHeading(transclude(scope, function() {}));
}
};
})
The require in this is not about require js it is actually saying that this directive requires a parent of type accordingGroup
take a look at the angular documentation for more info
https://docs.angularjs.org/guide/directive#creating-directives-that-communicate
From https://docs.angularjs.org/guide/directive
The ^^ prefix means that this directive searches for the controller on its parents. (A ^ prefix would make the directive look for the controller on its own element or its parents; without any prefix, the directive would look on its own element only.)
So basically you will have an error if your directive won't find the controller. Using require, you will be able to get access to the controller from the link method of directive.
link: function(scope, element, attrs, controller) {}
I see the following:
// my-directive.js
return {
require: 'ngModel',
scope: {
ngModel: '=',
},
controller: controller,
link: myLink
};
// my-link.js
return function(scope, $element, attrs, modelController) {
scope.onMyClick = function(event) {
modelController.$setViewValue(getItem(event));
}
};
// foo.html
<my-directive ng-model="myModel"></my-directive>
Is the ng-model directive used to provide a two way data-binding to the myModel in the outer scope where the my-directive instance is declared?
Is this the idiomatic way to provide two-way data binding between a directive and an outer model in Angular 1.4?
Does the require: 'ngModel' in the directive definition do anything other than inject the controller instance for the ng-model directive instance into the link function my-link?
Finally, does it make the controller of ng-model available on the scope for the controller of my-directive to use?
There are 2 different things here - ng-model as directive and ngModel: '=' in directive as attribute. Lets split them:
<my-directive ng-model="myModel" whatever="myModel"></my-directive>
return {
require: 'ngModel',
scope: {
myModel: '=',
},
controller: controller,
link: myLink
};
Is the ng-model directive used to provide a two way data-binding to
the myModel in the outer scope where the my-directive instance is
declared?
No, 2-way binding is provided by definition inside directive. (whatever here)
Is this the idiomatic way to provide two-way data binding between a
directive and an outer model in Angular 1.4?
One you used scope : { whatever : '='} is usual way.
Does the require: 'ngModel' in the directive definition do anything
other than inject the controller instance for the ng-model directive
instance into the link function my-link
Not that much, if u use directive without ngModel it will throw error.
Finally, does it make the controller of ng-model available on the
scope for the controller of my-directive to use?
No, it doesnt add anything to your scope. You just can access injected controller.
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
I have a directive with a require. Now I want to get the required controller instance AND the controller of the directive in the link function. How is that possible? If i set 'require' the fourth parameter of the link function only contains the required controller. If I do not set the requirement the fourth parameter of the link function contains the controller of the directive. How to get both?
You should require both, then the 4th argument will be an array of controllers (in the same order as the required directives.
E.g. from the source code of Angular's ngModel directive (which needs access to both NgModelController and its wrapping form's FormController):
var ngModelDirective = function() {
return {
require: ['ngModel', '^?form'],
controller: NgModelController,
link: function (scope, elem, attrs, ctrls) {
...
var modelCtrl = ctrls[0],
formCtrl = ctrls[1] || nullFormCtrl;
...