I have a couple nested directives. I'm trying to be consistent and use the controllerAs syntax. But I'm struggling to find a clean way for children to call parent methods that doesn't include a parent putting seemingly random functions on its scope.
angular.module('app', [])
.directive('parent', function(){
return {
restrict: 'EA',
controller: 'ParentController',
controllerAs: 'parentCtrl',
}
})
.directive('child', function(){
return {
restrict: 'EA',
require: '^parent',
controller: 'ChildController',
controllerAs: 'childCtrl'
}
})
.controller('ParentController', function($scope){
var ctrl = this;
ctrl.type = "hot";
ctrl.nonInheritedFunction = nonInheritedFunction;
$scope.inheritedFunction = inheritedFunction; // <-- trying to avoid this
function nonInheritedFunction(type){
ctrl.type = type;
if(ctrl.type == 'cold'){
//... do some Parent related things
}
}
function inheritedFunction(type){
// to illustrate that it does the same thing. Not a solution
ctrl.nonInheritedFunction(type);
}
})
.controller('ChildController', function($scope){
var ctrl = this;
ctrl.doAction = doAction;
function doAction(action){
if(action == 'flip_temperature'){
// bah
$scope.parentCtrl.nonInheritedFunction('hot');
// much cleaner feeling
$scope.inheritedFunction('hot');
// wishing I could do something like
// ctrl.nonInheritedFunction('hot');
}
}
/**
* template has access to type through `parentCtrl.type`
* and the function through `parentCtrl.nonInheritedFunction`
*
* The only access the ChildController has is `$scope.parentCtrl.type`
* Is there a way to keep $scope out of the equation?
*/
})
put transclude: true in your parent directive and manually pass the scope in the transclude function in your link function of parent.
Suppose,if the directive creates an isolate scope, the transcluded scope is now a child of the isolate scope. The transcluded and isolate scopes are no longer siblings. The $parent property of the transcluded scope now references the isolate scope.
In angular > 1.3, you're able to use bindToController to attach scope variable to your controller.
Let say you have a parent's function that bind in to scope
scope: {
onSomethingHappen: '&parentFunction'
},
controller: 'DirectiveController',
controllerAs: 'vm',
bindToController: true
You're able to call: this.onSomethingHappen() on your controller
You must use child and parent directives nested, so you can access the outer controller.
<parent >
<child >
</child></parent>
Related
Call a function on parent controller that is declared in is child directive.
Parent controller HTML
<div ng-click="testFunction()"></div>
Parent controller js
$scope.testFunction = function(){
$scope.functionFromChildDirective()
}
Child directive js
function TestDirective() {
return {
restrict: 'EA',
scope: {
},
templateUrl: '',
controller: function($scope) {
"ngInject";
$scope.functionFromChildDirective = function(){
console.log("TEST")
}
}
}
}
export default {
name: 'testDirective',
fn: TestDirective
};
Just delete the empty scope deceleration, by defining it you are creating a new isolate scope. If you don't declare the scope in the directive definition object it will just inherit the parents scope. However with this approach the child directive can only be used once (i.e can't be repeated) as each instance of will just overwrite the $scope.functionFromChildDirective property.
Use the ng-ref directive to bind the controller to a parent variable:
<test-directive ng-ref="testAPI">
</test-directive>
function TestDirective() {
return {
restrict: 'EA',
scope: {
},
templateUrl: '',
controller: function() {
this.testFn = function(){
console.log("TEST")
}
}
}
}
To invoke it:
<div ng-click="testAPI.testFn()"></div>
The ngRef directive tells AngularJS to assign the controller of a component (or a directive) to the given property in the current scope.
For more information, see AngularJS ng-ref Directive API Reference.
I need my parent controller function getDataInParent to get called in the parent's controller's context itself.
Sample:
The child directive is correctly setup like this from within the parent directive template:
<child get-data="parentCtrl.getDataInParent"></child>
And here is a simple mockup of my controller implementations (see comments in getDataInParent):
// *** parent controller
controller('parentController', [...., function(){
var that = this;
that.someData = ...;
that.getDataInParent = function(someArgs){
// Here I need to access "that.someData" and other
// properties/methods of this parent controller. But I can't!
// Because this method doesn't seemed to be called in the context of this controller.
};
};]
// *** child controller
controller('childController', [...., function(){
var that = this;
// Hooked up correctly and am able to call the parent's function like this:
var dataFromParent = $scope.getData().(someArgs);
};]
This seems like a very common scenario that a lot of people would have encountered, so I am hoping there should be a straight forward solution for this in Angular.
You can always create a local scope for a directive & bind the parent function to the local scope using this '&'. Suppose this is your html
<child get-data="parentCtrl.getDataInParent(params)"></child>
This should be your directive code.
angular
.module('SampleApp')
.directive('child', child);
/* #ngInject */
function child() {
var directive = {
restrict: 'E',
templateUrl: 'templateUrl',
scope: {
getData : '&'
},
link: linkFunc,
controller: 'Controller',
controllerAs: 'vm'
};
return directive;
function linkFunc(scope, el, attr, ctrl) {
// Calling parent method here
var dataFromParent = $scope.getData({params: "SomeParams"});
}
}
Working Plunker : https://plnkr.co/edit/4n61WF4JN3eT2QcdnnTw?p=preview
What is the better approach to get access to scope created by ng-if in my directive (without $parent.params.text):
<span ng-if="params" uib-tooltip="{{params.text}}"></span>
.directive('myDirective', functions(){
return {
templateUrl: 'template.html',
replace: true,
$scope: {
data: '='
},
controller: function(){
if (data) { //some logic
$scope.params.text = 'text'
}
}
}
})
I've noticed that I don't have to use $parent if my variable is nested inside an object.
For example:
controller
$scope.params = { ... }
view ng-if="params"
Won't work, but:
controller
$scope.something_here = {};
$scope.something_here.params = { ... }
view ng-if="something_here.params"
would work. I believe Angular preserves the scope if the key you're trying to access is part of an object. Give it a try!
You could use the controllerAs syntax.
add
controllerAs: "vm",
bindToController: true
as properties in your directive definition and replace $scope with vm.
Then refer to vm.params.text inside the ng-if
(function () {
'use strict';
angular
.module('app')
.directive('documentList', documentList);
documentList.$inject = ['$window'];
function documentList($window) {
var directive = {
restrict: 'E',
controller: controller,
controllerAs: "dl",
templateUrl: 'directives/document/document-list.html',
transclude: false,
scope: {
productRef: "=",
productSerialNumber: "=",
title: "#",
eid: "#"
},
};
return directive;
function controller($scope, $state, $element, documentService, ngProgressFactory, registrationService) {
var self = this;
self.goToDetailPage=goToDetailPage;
function goToDetailPage(docId) {
return "a";
}
})();
(function() {
'use strict';
angular
.module('app')
.controller('DetailCtrl', detailCtrl);
// Implementation of controller
detailCtrl.$inject = ['$scope', '$state', '$stateParams', '$rootScope'];
function detailCtrl($scope, $state, $stateParams, $rootScope) {
var self=this;
//alert($stateParams.docId)
self.a=$scope.dl.goToDetailPage();
}
})();
Above is the code in my directive and I have a controller where I want to call goToDetailPage function . But when I am trying to access it through var a=$scope.goToDetailPage() , I am getting error in console.
Not able to rectify.
Any help is appreciated!!!
Thanks
//DetailPage
.state('app.sr.detail', {
name: 'detail',
url: '/detail',
templateUrl: 'views/detail/detail1.html',
controller: 'DetailCtrl',
controllerAs: 'vm'
})
A cool pattern you can use when you want to be able to invoke a function that lives in a directive but you need to be able to invoke from your controller is to pass in an object using two way binding and then extend that object with a function inside the directive. Inside your directive pass in an additional value:
scope: {
productRef: "=",
productSerialNumber: "=",
title: "#",
eid: "#",
control: '=', // note that this is passed in using two way binding
}
Then extend that object inside your directive's controller by attaching a function to it:
// this is in your directive's controller
$scope.control.goToDetailPage = function() {
// function logic
}
Now define the control object in your Controller and pass it into the directive. Because it is two way bound the function will be applied and available to be called in either scope.
// in your controller, assuming controller as syntax
var vm = this;
vm.control = {};
// somewhere later you can invoke it
vm.control.goToDetailPage();
Maybe try,
$scope.goToDetailPage = function (docId) {
return "a";
}
Or, to make use of the "controller as" syntax,
var a = dl.goToDetailPage();
As it looks like from the snippets, DetailCtrl is parent of documentList directive. Functions in a child scope can't be accesses from parent controller. Consider defining goToDetailPage in DetailCtrl and inject that into the directive's scope using '&'
EDIT
If you have something like this:
<div ng-controller="DetailCtrl">
<document-list ...></document-list>
</div>
Then the controller DetailCtrl 's scope is the parent scope and the documentList directive's is the child. And since you are defining an isolate scope in documentList directive using scope: {...} parent and child scopes are different. And a method defined in the isolate scope can't be accessed directly in the parent scope using $scope.methodName().
You can however do the other way around, i.e. define the method in the DetailCtrl and inject into the directive's scope using this:
<div ng-controller="DetailCtrl">
<document-list .... detail-page="goToDetailPage()"></document-list>
</div>
And change your directive's scope to:
scope: { ...,
detailPage:'&'
},
Now you can make a call to the function in the directive controller by calling detailPage()
See the Angular Guide
Hope that helps
I'm aware how I can get parent's directive controller in the child directive's link function.
However, I'd prefer to avoid using link function (and $scope all-together) and have all my code under the controller function of the directive.
angular.directive('parent', function(){
return {
templateUrl: '/parent.html',
scope: true,
bindToController: true,
controllerAs: 'parentCtrl',
controller: function(){
this.coolFunction = function(){
console.log('cool');
}
}
}
});
angular.directive('child', function(){
return {
templateUrl: '/child.html',
require: '^parent',
scope: true,
bindToController: true,
controllerAs: 'childCtrl',
controller: function() {
// I want to run coolFunction here.
// How do I do it?
}
}
});
Any help is appreciated!
The proper pattern for that would be
app.directive('child', function(){
return {
templateUrl: '/child.html',
require: ['child', '^parent'],
scope: true,
bindToController: true,
controllerAs: 'childCtrl',
controller: function() {
this.coolFunction = function () {
this._parent.coolFunction();
}
},
link: function (scope, element, attrs, ctrls) {
var childCtrl = ctrls[0];
var parentCtrl = ctrls[1];
childCtrl._parent = parentCtrl;
}
}
});
The bad thing is that _parent is being exposed to scope with controllerAs, but it will rarely be a problem.
Notice that you won't have access to parent controller from child until link glues them together. Which is fine as long as you use parent controller in child methods.
Controller provides methods and initial properties to view model (and it does it cleaner with controllerAs), link glues the stuff, that's how directives work.
Both $scope and link have their purposes in Angular 1.x and are indispensable even with latest community developments. Banishing them for no valid reason is overzealous and may lead to bad design solutions. The absence of 'link' and 'scope' words in code won't help to make the app easier to port to 2.x. Though learning Angular 2 now and developing proper habits for 1.x will.
You could inject '$element' into the controller and access the parent controller like -
controller: ($element) ->
var parentCtrl = $element.parent().controller('parent');
parentCtrl.coolFunction();
//..........
//..........
This may not be the most transparent way of accessing 'any' parent controller because it requires the specific name of the directive and it is jqlite and not pure Angular.
Found this thread useful - How to access parent directive's controller by requiring it recursively?
EDIT: Thanks to #Dmitry for figuring out that angular doesn't need '.parent' to get the controller. Updated code -
controller: ($element) ->
var parentCtrl = $element.controller('parent');
parentCtrl.coolFunction();
//..........
See here or here
var parentForm = $element.inheritedData('$formController') || ....
var parentForm = $element.controller('form')