multiple directives on 1 element - angularjs

Given that I have the following two directives.
1: Angular UI select - this directive uses isolate scope.
2: myDirective - in my custom directive i also use isolate scope to access the value of ngModel
I am getting the Multiple directive error cannot share isolate scope. This is how I declare the isolate scope in my directive.
require: 'ngModel',
scope: {
modelValue: "=ngModel"
},
link: function (scope, el, attrs, ctrl) {
And i use it like:
<ui-select myDirective multiple ng-model="GroupsModel" theme="select2" ng-disabled="disabled" style="width: 300px;" hidden-text-box ">
<ui-select-match placeholder="groups">{{$item}}</ui-select-match>
<ui-select-choices repeat="color in Groups ">
{{color}}
</ui-select-choices>
</ui-select>
My question is, how I can get access to the ngmodel value from my custom directive if multiple directives cannot be used together on 1 element, is there a work around that will still keep the binding ?
Updated
I cannot access the required ng models value in the following function of my directive if I don't use empty the scope: {},
scope.reset = function () {
var modelValue =ctrl.$viewValue;
$timeout(function () {
el[0].focus();
}, 0, false);
};
Here is my directive:
var app = angular.module('app');
app.directive('resetField', [
'$compile', '$timeout', '$http', function ($compile, $timeout, $http) {
return {
require: 'ngModel',
link: function (scope, el, attrs, ctrl) {
// compiled reset icon template
var template = $compile('<i ng-show="enabled" ng-mousedown="reset()" class="fa fa-floppy-o" style="padding-left:5px"></i>')(scope);
el.after(template);
scope.reset = function () {
var modelValue =ctrl.$viewValue;
$timeout(function () {
el[0].focus();
}, 0, false);
};
el.bind('input', function() {
scope.enabled = !ctrl.$isEmpty(el.val());
})
.bind('focus', function() {
scope.enabled = !ctrl.$isEmpty(el.val());
scope.$apply();
})
.bind('blur', function() {
scope.enabled = false;
scope.$apply();
});
}
};
}
]);

If you are using isolated scope only to get the ng-model for the select, you can do it without using isolated scope.
In the link function just use scope[attrs.ngModel], you can even put a watch over it (as long as the ngmodel is a object property ng-model=obj.prop1)

You can use multiple directives on one element, the issue is that applying multiple isolated scopes on one element is invalid, you can use require to require another directive in myDirective:
angular.directive('myDirective', [function(){
return {
scope: false,
require: 'ngModel',
link: function(scope, element, attr, ctrl){
scope.modelValue = ctrl.$viewValue;
}
}
}])

Use 'require' in the directive to be able to access the controller of another directive. Then you are able to inject that controller in the params of the directives implementation. However, you cannot use an isolate scope if you do this.

if there are two directives that you need to use, make sure that the second directive is not an isolated one.

Related

AngularJS directive get inner element

I've got next directive:
(function() {
'use strict';
angular
.module('myApp')
.directive('inner', inner);
function inner () {
return {
restrict: 'A',
scope: false,
link: linkFunc
};
function linkFunc (scope, element, attrs) {
}
}
})();
And HTML:
<span inner>{{vm.number}}</span>
How can I access vm.number's value in linkFunc? I need to take value exactly from content of the span tag.
There are various ways you can do this but here are the 2 most common ways:
ngModel
You could use ng-model like so in your template:
<span inner ng-model="vm.number">{{vm.number}}</span>
In your directive you require the ngModel where you can pull its value:
.directive( 'inner', function(){
return {
require: 'ngModel',
link: function($scope, elem, attrs, ngModel){
var val = ngModel.$modelValue
}
}
})
declare isolate scope properties
<span inner="vm.number">{{vm.number}}</span>
.directive( 'inner', function(){
return {
scope: { inner:'=' } ,
link: function($scope, elem, attrs){
var val = $scope.inner
}
}
})
Some less common ways:
use $parse service to get the value
Using the template again:
<span inner="vm.number">{{vm.number}}</span>
Let's assume you're going to Firstly you'll need to inject the $parse service in your directive's definition. Then inside your link function do the following:
var val = $parse(attrs.inner)
inherited scope for read only
I don't recommend this, because depending on how you defined your directive's scope option, things might get out of sync:
isolate (aka isolated) scopes will not inherit that value and vm.number will probably throw an undefined reference error because vm is undefined in most cases.
inherited scope will inherit the initial value from the parent scope but could diverge during run-time.
no scope will be the only case where it will stay in sync since the directive's $scope reference is the same scope present in the expression {{vm.number}}
Again I stress this is probably not the best option here. I'd only recommend this if you are suffering performance issues from a large number of repeated elements or large number of bindings. More on the directive's scope options - https://spin.atomicobject.com/2015/10/14/angular-directive-scope/
Well, In Angular directive, Link function can do almost everything controller can.
To make it very simple, we use one of them most of the time.
var app = angular.module('app', []);
app.controller('AppCtrl', function ($scope) {
$scope.number = 5;
}).directive('inner', function () {
return {
restrict: 'A',
scope: false,
link: function (scope, element, attrs) {
var number = scope.number;
console.log(number);
}
}
});
Inside html :
<div inner ng-model="number">{{number}}</div>
https://plnkr.co/edit/YbXYpNtu7S3wc0zuBw3u?p=preview
In order to take value from HTML, Angular provides ng-model directive which is works on two way data binding concepts.
There are other ways which is already explain by #jusopi :)
cheers!

Require a directive's controller that is nested in a parent directive, AngularJS

I'm wondering if there is a way to require the controller of a directive that exists/is nested somewhere as a common parent's child directive in AngularJS.
Directive Structure
Suppose I have the following structure for my directives:
<parent-directive>
<ul>
<li some-nested-directive ng-repeat="dir in directives"></li>
</ul>
<settings-menu></settings-menu>
</parent-directive>
Directive Definition
/*
* some-nested-directive directive definition
*/
.directive('someNestedDirective', function(){
// ...
return {
restrict: 'A',
controller: someNestedDirectiveController
};
});
/*
* settings-menu directive definition
*/
.directive('settingsMenu', function(){
return {
restrict: 'AE',
require: [], // how to require the nested-directive controller here?
link: function(scope, iElement, attrs, ctrls){
// ...
}
};
})
I've already checked out this SO question which states how to require controllers of directives that exist along the same line in a hierarchy.
But my question is regarding a way to do the same in a hierarchy of directives that NOT necessarily exist along the same line. And if this is not possible, what is a proper workaround for it. Any help would be appreciated.
EDIT
Also, can any of the prefixes for require (or a combination of them) be used to achieve the same?
One approach is to use parent directive as a way to pass references between controllers:
var mod = angular.module('test', []);
mod.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: '<div>Parent <div ng-transclude=""></div></div>',
controller: function ParentCtrl() {}
}
});
mod.directive('dirA', function() {
return {
restrict: 'E',
template: '<div>Dir A <input type="text" ng-model="name"></div>',
require: ['dirA', '^^parent'],
link: function(scope, element, attrs, ctrls) {
//here we store this directive controller into parent directive controller instance
ctrls[1].dirA = ctrls[0];
},
controller: function DirACtrl($scope) {
$scope.name = 'Dir A Name';
this.name = function() {
return $scope.name;
};
}
}
});
mod.directive('dirB', function() {
return {
restrict: 'E',
template: '<div>Dir A <button ng-click="click()">Click</button></div>',
require: ['dirB', '^^parent'],
link: function(scope, element, attrs, ctrls) {
//let's assign parent controller instance to this directive controller instance
ctrls[0].parent = ctrls[1];
},
controller: function DirBCtrl($scope) {
var ctrl = this;
$scope.click = function() {
//access dirA controller through parent
alert(ctrl.parent.dirA.name());
};
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='test'>
<parent>
<dir-a></dir-a>
<dir-b></dir-b>
</parent>
</div>
When using above approach it also makes sense to encapsulate how the dirA controller is stored inside parent controller i.e. by using a getter property or by exposing only the required properties of dirA controller.
I aggree with miensol's reply and I recommend that approach but in some cases you may need something like that;
<parent-directive>
<ul>
<some-nested-directive id="snd1" ng-repeat="dir in directives"></some-nested-directive>
</ul>
<settings-menu some-nested-directive-id="snd1"></settings-menu>
You can access the scope of some-nested-directive using its id from the settings-menu;
$("#" + scope.someNestedDirectiveId).scope()
Once I used this approach to cascade the values of a dropdown according to the choise of another independent dropdown.

How can I access a directive's ngModelController from another directive?

How can I access a directive's ngModelController from another directive?
The scenario
I'm creating a type ahead widget, which is composed of a typeAhead directive and autoCompletePopUp directive.
AutoCompletePopUp directive will interact with the typeAhead using typeAhead's controller.
But I don't know how to call typeAhead's $setViewValue from autoCompletePopUp when an item is selected.
Why not just add a function to the controller for typeAhead that calls $setViewValue on itself. In the context of typeAhead's controller you should have access to the scope. You can put the ngModelController for typeAhead on the scope if needed. Something like this:
angular.module("myModule").directive("typeAhead", function() {
return {
require: "ngModel",
controller: function($scope) {
this.setValue = function(value) {
$scope.ngModelController.$setViewValue(value);
};
},
link: function(scope, element, attributes, ngModelController) {
scope.ngModelController = ngModelController;
},
};
});
angular.module("myModule").directive("typeAhead", function() {
return {
require: "typeAhead",
link: function(scope, element, attributes, typeAheadController) {
scope.someAction = function(value) {
typeAheadController.setValue(value);
};
},
};
});
(protect against minification and move controllers into separate objects / files as desired; done inline here for convenience)

In an AngularJs directive, how do I call its controller's function contained within an directive element's attribute?

I am trying to call a function in a controller, which is part of a custom angular directive, following is the code,
Method 1: (Doesn't work: Controller's function doesn't write to the console)
HTML:
<div ng-app="MyApp">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
controller: function ($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
}
}
});
When I remove the directive's controller from it and and create a new controller it works,
Method 2: (Works: Controller's function does write to the console)
HTML:
<div ng-app="MyApp" ng-controller="Ctrl">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
}
}
});
app.controller('Ctrl', function ($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
});
I would like know how to get the behavior of Method 2 in Method 1 i.e., to be able to call the directive's controller's function from directive's attribute.
Any help is greatly appreciated, Thank you!
In Method 1, you are creating an isolated scope and defining a scope value someCtrlFn that takes in a function from the parent scope that is using your directive. The function to use is specified by the attribute callbackFn.
The way directives work with these scope items is that they are expected to be assigned from things that are on the parent scope that is active when the directive is used. So, if you have a controller Ctrl as in your Method 2, then use the directive within that scope, your directive is trying to match the what you defined in the attribute to what is available on Ctrl's scope.
So, in your first example, it's looking for a function called ctrlFn on the parent scope, but there isn't one. It will not try to look for it on the directive's controller. This is why Method 2 works, because there is a parent scope where ctrlFn is defined, and the directive is able to properly invoke that expression.
The purpose of these scope attributes is to allow directives to bind to values or functions on a parent scope to facilitate communication. For example, to give the directive data that it will display or modify, or allow the parent to define a function the directive can invoke for a callback during an event or what have you. The parent scope cannot move into the directive's scope and force the directive's scope to use its own defined items (unless you set it up so your directive uses a default value or function if the attribute is omitted or whatever).
They are not used so a directive can define things on its scope that it uses internally. If these things are internal to the directive, you can simply add them to the scope during link or whatever is suitable.
Did you mean something like this?
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { },
link: function(scope, element, attrs) {
// defines ctrlFn that can be used later by this directive's template or controller
scope.ctrlFn = function(test) {
console.log(test);
}
// immediately invokes ctrlFn to log a message, just here to illustrate
scope.ctrlFn('Hello World!');
}
}
});
Achieved it using $rootScope instead of $scope within the directive's controller
HTML:
<div ng-app="MyApp">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
<script>
var app = angular.module('MyApp', []);
app.directive('myDirective', function($rootScope) {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
controller: function ($scope) {
$rootScope.ctrlFn = function(test) {
console.log(test);
}
}
}
});
</script>

How do directives communicate by a controller?

In this document: http://docs.angularjs.org/guide/directive, it says directives may communicate with each other by controllers.
controller - Controller constructor function. The controller is instantiated before the pre-linking phase and it is shared with other directives if they request it by name (see require attribute). This allows the directives to communicate with each other and augment each other's behavior.
I don't understand it well, how they communicated with controllers? Is there any example or demo for it?
Perhaps you're confusing a controller that is created when a route change occur with a directive controller. That section is only talking about directive controllers, so what that section means is that if you have two directives on the same HTML element, they can communicate by requiring each others controllers.
A good example of that is if your creating a directive that needs to communicate with ng-model. Since ng-model exposes a controller, you can require it like this:
myApp.directive('myDirective', function() {
return {
require: 'ngModel',
link: function($scope, elem, attrs, ngModelCtrl) {
// Here you can listen to different DOM events, and
// call ngModelCtrl when the model value needs to update
}
}
});
And the HTML:
<input type="text" ng-model="myModel" my-directive>
Your directive can expose a controller by implementing it in the object that your directive function returns, like this:
myApp.directive('myDirective1', function() {
return {
link: function($scope, elem, attrs) {
},
controller: function() {
this.sayHello = function() {
alert("hello!");
}
}
}
});
myApp.directive('myDirective2', function() {
return {
require: 'myDirective1',
link: function($scope, elem, attrs, myDirective1Ctrl) {
myDirective1Ctrl.sayHello();
}
}
});
And the HTML:
<input type="text" my-directive2 my-directive1>
You can also require a directive controller from a parent directive by writing require: '^myParentDirective', like this:
myApp.directive('myDirective1', function() {
return {
link: function($scope, elem, attrs) {
},
controller: function() {
this.sayHello = function() {
alert("hello!");
}
}
}
});
myApp.directive('myDirective2', function() {
return {
require: '^myDirective1',
link: function($scope, elem, attrs, myDirective1Ctrl) {
myDirective1Ctrl.sayHello();
}
}
});
And the HTML:
<div my-directive1>
<div my-directive2></div>
</div>

Resources