I have a template that goes something like this:
<parent-directive>
<child-directive binding="varFromParent"></child-directive>
<button ng-click="parentDirective.save()"></button>
</parent-directive>
When executing a function in the parentDirective controller, is it possible to access and manipulate the scope variables of the childDirective for e.g. if I have them set up as so
angular.module('app').directive('parentDirective', function() {
return {
restrict: 'E',
templateUrl: '...',
controllerAs: 'parentDirective',
controller: function($rootScope, $scope) {
//...
this.save = () => {
//Need to manipulate childDirective so that its
//scope.defaultValue == 'NEW DEFAULT'
}
}
}
});
and
angular.module('app').directive('childDirective', function() {
return {
restrict: 'E',
templateUrl: '...',
scope: {
binding: '='
},
controllerAs: 'childDirective',
link: function(scope, elm, attrs) {
scope.defaultValue = 'DEFAULT';
}
}
});
How would I go about doing this? Is there any way to do this without setting up a bidirectional binding? I would like to avoid a mess of attributes on the <child-directive> element if possible.
There are many way to set up a communication between your children and your parent directive:
Bidirectional binding (like you said)
Registration of your children in your parent.
You can use the directive require property and the last parameter of the link function controllers to register a children in his parent.
Events, see $scope.on/broadcast
Angular services (as they are "singletons", it's very easy to use it to share data between your directives)
etc.
Example for 2:
angular.module('Example', [])
.directive('parent', [function () {
return {
controller: function (){
// registerChildren etc
}
// ...
};
}])
.directive('children', [function () {
return {
require: ['^^parent', 'children'],
controller: function (){
// ...
}
link: function ($scope, element, attributs, controllers) {
ParentController = controllers[0];
OwnController = controllers[1];
ParentController.registerChildren(OwnController);
// ...
}
// ...
};
}])
In this case you probably don't need to isolate child's directive scope. Define a variable you need to change on parent's scope and then child's directive scope would inherit this value so you can change it value in child's directive and it would be accessible from parent.
angular.module('app').directive('parentDirective', function() {
return {
restrict: 'E',
controllerAs: 'parentCtrl',
controller: function($scope) {
$scope.value = 'Value from parent';
this.value = $scope.value
this.save = function() {
this.value = $scope.value;
}
}
}
});
angular.module('app').directive('childDirective', function() {
return {
restrict: 'E',
controllerAs: 'childCtrl',
controller: function($scope) {
$scope.value = 'Value from child';
this.setValue = function() {
$scope.value = 'New value from child';
}
}
}
});
Here is the fiddle
http://jsfiddle.net/dmitriy_nevzorov/fy31qobe/3/
Related
How can I pass a child attribute directive's scope or attr value to a parent directive?
Given widget directive, with in-viewport attribute directive, I want to update the attribute inView each time the document is scrolled, and pass the updated value to the parent directive widget:
<widget in-viewport></widget>
In Viewport directive: passed in as an attribute of parent directive "widget"
angular.module('app').directive('inViewport', function() {
return {
restrict: 'A',
scope: false, // ensure scope is same as parents
link: function(scope, element, attr) {
angular.element(document).on('scroll', function() {
// I've tried binding it to attr and parent scope of "widget" directive
attr.inView = isElementInViewport(element);
scope.inView = isElementInViewport(element);
});
}
};
});
Widget Directive:
angular.module('app').directive('widget', function() {
return {
restrict: 'AE',
scope: {
inView: '='
},
transclude: false,
templateUrl: 'directives/widgets/widgets.tpl.html',
link: function(scope) {
console.log('In Viewport: ', scope.inView); // Null
Here is the way you can access parent directive variables,
angular.module('myApp', []).directive('widget', function() {
return {
restrict: 'E',
template: '<viewport in-view="variable"></viewport> <h1>{{variable}}</h1>',
link: function(scope, iAttrs) {
scope.variable = 10;
}
}
}).directive('viewport', function() {
return {
restrict: 'E',
scope: {
inView: "=",
},
template: '<button ng-click="click()">Directive</button>',
link: function(scope, iElement, iAttrs) {
scope.click = function() {
scope.inView++;
}
}
}
});
HTML
<div ng-app="myApp" ng-controller="Ctrl1">
<widget></widget>
</div>
Here is the working jsfiddle
http://jsfiddle.net/p75DS/784/
If you have any question, ask in the comment box
Here is a working fiddle using your directive structure:
http://jsfiddle.net/ADukg/9591/
Markup is like this:
<div ng-controller="MyCtrl" style="height: 1200px;">
{{name}}
<hr>
<widget in-viewport></widget>
</div>
Just scroll the window to trigger the event. Note that the parent directive has a watch just to prove that the var gets updated...
var myApp = angular.module('myApp',[]);
myApp.directive('inViewport', function($timeout) {
return {
restrict: 'A',
scope: false, // ensure scope is same as parents
link: function(scope, element, attr) {
angular.element(window).bind('scroll', function() {
console.log('Called');
$timeout(function() {
scope.inView++;
}, 0);
});
}
};
});
myApp.directive('widget', function() {
return {
restrict: 'AE',
transclude: false,
template: '<p>This is a widget</p>',
link: function(scope) {
scope.inView = 0;
console.log('In Viewport: ', scope.inView); // Null
scope.$watch('inView', function(newVal, oldVal) {
console.log('Updated by the child directive: ', scope.inView);
});
}
}
});
function MyCtrl($scope) {
$scope.name = 'Angular Directive Stuff';
}
You can expose an API on your parent directive and use isolateScope() to access it.
Here's a working fiddle.
var app = angular.module("app",[]);
app.directive("widget", function($rootScope){
return {
template: "<div>Scroll this page and widget will update. Scroll Y: {{scrollPosY}}</div>",
scope: {}, // <-- Creating isolate scope on <widget>. This is REQUIRED.
controller: ['$scope', function DirContainerController($scope) {
$scope.scrollPosY = 0;
// Creating an update function.
$scope.update = function(position) {
$scope.scrollPosY = position;
$scope.$digest();
};
}],
}
});
app.directive("inViewport", function($window, $timeout, $rootScope){
return {
restrict: 'A',
link:function(scope, element, attrs, parentCtrl){
// Get the scope. This can be any directive.
var parentScope = element.isolateScope();
angular.element(document).on('scroll', function() {
// As long as the parent directive implements an 'update()' function this will work.
parentScope.update($window.scrollY);
console.log('parentScope: ', parentScope);
});
}
}
});
Here is the problem. I have some 3rd party directive called main-directive.
app.directive('mainDirective', function() {
return {
scope: {
foo: '&'
// attrs
},
controller: function($scope) {
$scope.click = function() {
window.alert($scope.foo());
}
},
template: '<button ng-click="click()">Click me</button>'
}
});
So I want to make my own directive called parent-directive which assign application specific default values to third party directive attributes.
app.directive('parentDirective', function() {
return {
scope: {
foo: '&?',
attr2: '='
// lots of attrs
},
controller: function($scope) {
$scope.attr1 = "some default value"
$scope.foo = function() {
return "not overrided"
}
if (this.foo) {
$scope.foo = this.foo
}
},
template: '<div class="some-styling"><main-directive foo="foo()" attr1="attr1" attr2="attr2"></main-directive></div>'
}
});
What if I want to make another child-directive that keeps parent-directive logic.
Overloading attribute is easy i can use "compile" function. But what about overriding functions is it possible?
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
link: function(scope, element, attr, controller) {
controller.foo = function() {
return "overrided";
}
},
compile: function(element, attr) {
attr.attr2 = "attr2";
}
}
});
Whole thing can be easily done by using child scope instead of isolated.
Or by using extending by template. But if I extends directive with template I would have to copy parent "scope" and "template" definition to child-directive and forward all the non-default attributes this doesn't seem like an elegant solution.
So the key question, is there a way to override parent-directive function using isolated scope without forwarding attributes.
Here is the DEMO
Ok, I have done some research and it turns out that there can be several approaches there
Scope inheritance
Since child-directive is not creating own scope it just creating new methods at parent-directive parent scope. So we can modify attributes during compile and specify overridden foo method.
app.directive('parentDirective', function() {
return {
scope: {
fooImpl: '&?',
// lots of attrs
},
controller: function($scope) {
$scope.foo = function() {
if ($scope.fooImpl) {
return $scope.fooImpl();
}
return "not overrided";
}
},
template: '<div class="some-styling"><main-directive foo="foo()"></main-directive></div>'
}
});
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
controller: function($scope) {
$scope.foo = function() {
return "overrided";
}
},
compile: function(element, attr) {
attr.fooImpl = "foo()";
}
}
});
Here is the DEMO1
Add to isolated scope
Angular provides special function. That can get isolated scope from element. So we can override our method during linking phase.
app.directive('parentDirective', function() {
return {
scope: {
fooImpl: '&?',
// lots of attrs
},
controller: function($scope) {
$scope.foo = function() {
if ($scope.fooImpl) {
return $scope.fooImpl();
}
return "not overrided";
}
},
template: '<div class="some-styling"><main-directive foo="foo()"></main-directive></div>'
}
});
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
link: function(scope, element, attr) {
var innerScope = angular.element(element[0]).isolateScope();
innerScope.foo = function() {
return "overrided";
}
}
}
});
Here is the DEMO2
Controller method
If we use controllerAs syntax. That means we exposing controller object variables as a scope. We can override function in child directive during linking phase.
app.directive('parentDirective', function() {
return {
scope: {
fooImpl: '&?',
// lots of attrs
},
controller: function($scope) {
var vm = this;
vm.foo = function() {
return "not overrided";
}
},
controllerAs : 'vm',
template: '<div class="some-styling"><main-directive foo="vm.foo()"></main-directive></div>'
}
});
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
link: function (scope, element, attr, controller) {
controller.foo = function() {
return "overrided";
}
}
}
});
Here is the DEMO3
Transclusion
Practically you can do the same thing with seperate parent and child directive and using transclusion. But anyway it would be combination of above approaches. Thanks for "Extending an existing directive in AngularJS"
I have several hierarchical directives and in one, I need to have some functions in its controller, so that the child elements can interact with it. But this one directive also needs to reference its parent directive's controller, but I don't know how to do that in controller (I know how in the "link()" but this time I need controller for the child interaction). It should be possible to do it with scope:
controller: function($scope){},
link: function (scope, ..., parentCtrl){
scope.parentCtrl = parentCtrl;
}
but it seems weird, because the link function is executed after the controller is, or it it OK? I'm confused and I think it might be a bad design?
diagram:
ParentParentDirective
controller: function(service){
this.service = service;
}
ParentDirective
controller: function(){
this.callOnService = function(id){
???ParentParentDirective???.service.callSth(id);
}
}
ChildDirective
link(scope, ...,ParentDirectiveCtrl){
scope.makeChanges = ParentDirectiveCtrl.callOnService;
}
You can use $element.controller method for that, as in example below.
angular.module('app', []);
angular.module('app').directive('grandparent', function () {
return {
restrict: 'E',
controller: function () {
this.go = function () {
console.log('Grandparent directive');
};
}
};
});
angular.module('app').directive('parent', function () {
return {
restrict: 'E',
controller: function () {
this.go = function () {
console.log('Parent directive');
};
}
};
});
angular.module('app').directive('child', function () {
return {
restrict: 'E',
require: ['^parent', '^grandparent'],
controller: function ($element) {
var parentCtrl = $element.controller('parent');
var grandparentCtrl = $element.controller('grandparent');
parentCtrl.go();
grandparentCtrl.go();
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<div ng-app="app">
<grandparent>
<parent>
<child></child>
</parent>
</grandparent>
</div>
Please consider this tryout on Plunkr.
I have a simple set up:
<body ng-app="myApp">
<div ng-controller="myController">
<parent-directive></parent-directive>
<child-directive></child-directive>
</div>
</body>
With the parent directive defined like:
app.directive("parentDirective", [
"$compile",
function (
$compile) {
return {
scope: {
person: "="
},
restrict: "E",
template: "<h3>I'm a parent</h3>",
controller: [
"$scope",
function ($scope) {
// --- PRIVATE --- //
var self = {};
$scope.ClickMe = function() {
alert('Parent clicked');
};
}],
link: function ($scope, $elem, $attrs) {
}
};
}]);
And child directive defined like:
app.directive("childDirective", [
"$compile",
function (
$compile) {
return {
scope: {
person: "="
},
restrict: "E",
require: "^?parentDirective",
template: "<h3>I'm a child, click <button ng-click='ClickMe()'>here</button></h3>",
controller: [
"$scope",
function ($scope) {
// --- PRIVATE --- //
var self = {};
$scope.ClickMe = function() {
alert('child clicked');
$scope.parentDirective.ClickMe();
};
}],
link: function ($scope, $elem, $attrs) {
}
};
}]);
The child click is handled, but the click defined on the `parent', returns undefined:
TypeError: Cannot read property 'ClickMe' of undefined
looking at the console.
Any idea what's going wrong?
Any idea what's going wrong?
You cannot require a sibling directive.
The required directives controller methods dont get exposed automagically onto your scope.
You should expose methods on the controller itself, not on the assigned $scope.
You can require a directive that is defined on the same element as the requiring directive, or on a parent element.
<child-directive parent-directive></child-directive>
<parent-directive>
<child-directive></child-directive>
</parent-directive>
When you require the controller (aka. exposed API) of another directive, it doesn't magically end up on the $scope of the requiring directive.
It does however end up in your link function as the fourth argument.
Like so:
.directive('child', function () {
return {
require: '?^parentDirective',
link: function (scope, el, attrs, parentDirectiveController) {
scope.clickMe = function () {
parentDirectiveController.clickMe();
};
}
};
});
Expose the methods you want available in other directives onto this instead of $scope, as the $scope way of doing it won't work the way you intend it to when you have isolated scopes.
.directive('parent',
controller: function () {
this.clickMe = function () {};
}
}
To get your example working;
<parent>
<child></child>
</parent>
.directive('parent', function () {
return {
controller: function () {
this.clickMe = function () {};
}
};
}
.directive('child', function () {
return {
require: '^?parent',
link: function (scope, el, attrs, parentCtrl) {
scope.clickMe = function () {
parentCtrl.clickMe();
};
}
};
});
Simplified (& working) version of your plunker: http://plnkr.co/edit/nao4EvbptQm7gDKkmZS2?p=preview
Put your child directive in your parent directive template. Then use $scope.$parent.ClickMe(). Here's what it would look like.
Simple setup:
<body ng-app="myApp">
<div ng-controller="myController">
<parent-directive></parent-directive>
</div>
</body>
Parent directive:
app.directive("parentDirective", [
function () {
return {
scope: {},
restrict: "E",
template: "<h3>I'm a parent</h3><child-directive></child-directive>",
controller: [
"$scope",
function ($scope) {
$scope.ClickMe = function() {
alert('Parent clicked');
};
}
]
};
}
]);
Child directive:
app.directive("childDirective", [
function () {
return {
restrict: "E",
template: "<h3>I'm a child, click <button ng-click='ClickMe()'>here</button></h3>",
controller: [
"$scope",
function ($scope) {
$scope.ClickMe = function() {
alert('child clicked');
$scope.$parent.ClickMe();
};
}
]
};
}
]);
I might be thinking about your problem a little differently but I would take a look at $broadcast. The idea is you can broadcast an event and have "n" number of directives in your case listening for that event.
http://plnkr.co/edit/wBmX2TvC3yMXwItfxkgl
brodcast:
$scope.ClickMe = function() {
alert('child clicked');
$rootScope.$broadcast('child-click');;
};
listen:
$scope.$on('child-click', function (event, args) {
alert('Parent clicked');
});
Currently, I am facing one issue related to angularjs directive. I want to send outlet object from directive1 to directive2. Both directives having same controller scope. I tried with emitting event from directive1 to controller, broadcasting that event from controller to directive2 and listening to that event on directive2. but that is not working.
Directive1:
angular.module('moduleName')
.directive('directive1', function() {
return {
restrict: 'E',
templateUrl: 'directive1.html',
scope: false,
link: function(scope) {
scope.selectOutlet = function(outlet) {
scope.order.entityId = outlet.id;
scope.navigation.currentTab = 'right';
};
}
};
Here, in directive1, scope.selectOutlet() setting outletId to scope.order.entityId. I want to move/set that line to directive2 save function.
Directive2:
angular.module('moduleName')
.directive('directive2', function(config, $rootScope, $state) {
return {
restrict: 'E',
templateUrl: 'directive2.html',
scope: false,
link: function(scope) {
scope.save = function() {
// Save functionality
// scope.order.entityId = outlet.id; This is what i want to do
};
}
};
});
});
Any help.
you can use a factory or a service. Inject that factory into your directive. Now when you are trying set the data in function written into factory. `app.factory('shared',function(){
var obj ={};
obj.setData = function(){
// call this function from directive 1.
}
return obj;
})`
So if you include this factory into your directives you will get the data in 2 directives.
I will try to make some jsfiddle or plunker. If it is not clear.
Do the following in first directive
angular.module('moduleName')
.directive('directive1', function($rootScope) {
return {
restrict: 'E',
templateUrl: 'directive1.html',
scope: false,
link: function(scope) {
scope.selectOutlet = function(outlet) {
$rootScope.$broadcast('save:outlet',outlet);
//scope.order.entityId = outlet.id;
//scope.navigation.currentTab = 'right';
};
}
};
and in second
angular.module('moduleName')
.directive('directive2', function(config, $rootScope, $state) {
return {
restrict: 'E',
templateUrl: 'directive2.html',
scope: false,
link: function(scope) {
$rootScope.$on('save:outlet',function(event,data){
// do staff here
});
}
};
});