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
Related
I want to access bindings in a transclude block, here a litte example:
<datagetter>
<dataviewer data="$ctrl.data"></dataviewer>
</datagetter>
The datagetter component get some data via service and store it in a local variable e.g. this.data ( $ctrl.data in tempalte) - now i want to acces this data in the component that is set in the transclide block "dataviewer".
To solve this i read this articles, who come close to my problem:
Passing a binding to transcluded scope in component
AngularJS - access directive scope from transclude scope
I know i can use require to get the parentconroller in the childcomponent but this is not an option because i want to stay generic, for example it could be another "dataviewer" that takes the same data from "datagetter", also it should be used in a scenario where the "dataviwer" gets data from another datagetter.
is there a suitable solution for my needs? any help is appreciated
You need to do manual transclution, that's what I did in a similar situation.
Anglers default transclution won't work, since it creates a new scope.
<datagetter>
<dataviewer data="$ctrl.data"></dataviewer>
</datagetter>
/**
* Parent component
*/
angular.module("app").component("datagetter", function() {
constructor() {
return {
restrict: 'E',
replace: true,
template: "<div transclude-extended></div>",
transclude: true,
bindToController: true,
controller: function () {
var ctrl = this;
ctrl.data = 'This is sample data.';
}
};
}
});
/**
* Transclude Extended Directive
*/
angular.module("app").directive("transcludeExtended", function(){
constructor() {
return {
link: function ($scope, $element, $attrs, controller, $transclude) {
var innerScope = $scope.$new();
$transclude(innerScope, function (clone) {
$element.empty();
$element.append(clone);
$element.on('$destroy', function () {
innerScope.$destroy();
});
});
}
}
};
});
transcludeExtended is the manual way of doing the translation, instead of ng-transclude
Reference: https://github.com/angular/angular.js/issues/1809
I'm testing the controller of a directive that has scope set to false in the directive definition object. A method from the 'parent' controller is called from the directive controller - I am unable to stub this method as I have no access to it from my directive's controller's test file though - what is the best procedure for dealing with this situation?
Many thanks.
You could pre-define the known methods and set them to angular.noop:
function ChildController() {
var _this = this;
_this.parentMethod1 = angular.noop;
_this.parentMethod2 = angular.noop;
_this.childMethod = function() {
// do some stuff
};
}
Then you can create an init method on the child that is called during link that replaces the methods with the parent methods if the parent is there:
// directive definition
.directive('myDirective', function myDirective() {
return {
bindToController: true,
controller: ChildController,
controlelrAs: 'childController',
require: ['childController', '^parentController'],
scope: false,
link: function (scope, element, attributes, controllers) {
// controllers[0] = childController
// controllers[1] = parentController
controllers[0].init(controllers[1]);
}
};
});
// child controller
function ChildController() {
// ...same as above...
_this.init = function(parentController) {
_this.parentMethod1 = parentController.parentMethod1;
_this.parentMethod2 = parentController.parentMethod2;
};
}
You could also define the controller using .controller to make testing simpler (without needing to create the directive with a mock parentController). But this should get what you described done.
(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 have two directives which both have an isolated scope. I would like to require the parent directive in my child directive, and then be able to watch if a scope variable of the parent changes. I don't want to modify the variable in the child, I just want to be able to read it.
I want to be able to add different children which both have access to the list, but I don't want to have to bind the list to every child. What is missing in the example below, is a way to watch the list which gets bound to the parent. I am able to pass the original list in, but as soon as it updates, the child will have an outdated model.
Parent directive:
angular
.module('app.parent', [])
.directive('parent', parent);
function parent() {
var directive = {
restrict: 'EA',
transclude: true,
template: '<div>parent <pre>{{vm.list}}</pre><ng-transclude></ng-transclude> </div>',
scope: true,
controller: ParentController,
controllerAs: 'vm',
bindToController: {
config: "=",
list: "="
}
};
return directive;
function ParentController() {
var vm = this;
}
}
child directive:
angular
.module('app.parent.child', ['app.parent'])
.directive('child', child);
function child() {
var directive = {
restrict: 'EA',
require: ['^^parent', '^child'],
template: '<div>child<pre>{{vm.list}}</pre></div>',
scope: true,
controller: ChildController,
link: linkFunc,
controllerAs: 'vm',
bindToController: {
config: "="
}
};
return directive;
function ChildController() {
var vm = this;
}
function linkFunc(scope, element, attrs, ctrls) {
var parentController = ctrls[0];
var vm = ctrls[1];
vm.list = parentController.list;
}
}
I have made a Plunkr with the code above. I am looking for a nice pattern to solve the issue I am having. Both directives will have their own unique config object passed in with configurations specific to the directive.
You can create a watcher on the child directive's scope object, but rather than watching a scope item, you can pass in a function as the first parameter to $watch() and simply return a value/object that you would like to watch.
So for instance inside your child directive's linkFunc()
scope.$watch(function() {
return parentController.list;
}, function(newList) {
vm.list = newList;
});
Modified your plunkr: http://plnkr.co/edit/6WzT8PQJRH1b5KuU0twn?p=preview
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>