I have a nested directive. I am trying to access the scope of the parent directive (which is isolated) but can't seem to make it work. I get undefined errors when trying to log it out to the console.
Here's an example of what I am trying to get to work.
app.directive("myParentControl", function() {
return {
restrict: "A",
scope: {},
controller: function($scope) {
$scope.propertyOne = "PropertyOne"
},
link: function(scope, element) {
console.log(scope.propertyOne);
}
}
});
app.directive("myChildControl", function() {
return {
require: "^myParentControl",
link: function(scope, element, attrs, myParentControlCtrl) {
//Undefined
console.log(myparentControlCtrl.propertyOne);
//Not visible in scope inspector
myParentControlCtrl.newValue = "New Value";
}
}
})
You are setting the variable to the $scope: $scope.propertyOne = "PropertyOne", but try to access it from the controller: console.log(myparentControlCtrl.propertyOne). Of course it is undefined.
Set the property in the controller:
controller: function($scope) {
this.propertyOne = "PropertyOne";
},
If you need to access it from the template of myParentControl, put the controller in the scope using the controllerAs property:
app.directive("myParentControl", function() {
return {
...
controllerAs: "ctrl",
...
};
});
From the template access it as:
<span>{{ ctrl.propertyOne }</span>
You can directly access the scope of the parent directive using scope in child directives.
myApp.directive("myChildControl", function() {
return {
require: "^myParentControl",
link: function(scope, element, attrs, myParentControl) {
console.log(scope.propertyOne);
//Not visible in scope inspector
myParentControl.newValue = "New Value";
}
}
})
SEE DEMO HERE
Related
I'm trying to inherit an attribute from a parent custom directive with isolate scope. In the example below, I want to be able to access the api attribute on myParent from the myChild controller or link function. My end goal is to inject an instance of the api that can be accessed by the children and from the view controller.
<my-parent api="parentInstance1">
<my-child ng-repeat="field in ::data"
ng-attr-src="{{field.src||undefined}}"
</my-child>
</my-parent>
<my-parent api="parentInstance2">
<my-child ng-repeat="field in ::data"
ng-attr-src="{{field.src||undefined}}"
</my-child>
</my-parent>
A simplified version of both directives looks like this
app.directive('myParent', function () {
return {
transclude: true,
restrict: "E",
scope: {
api: '=?'
},
template: '...',
controller: function ($scope, $attrs ) {
// foo is injected from a factory instance
function foo ( ) {
}
$scope.api = {
foo: foo
}
},
link: function ($scope, $element, $attr) {
}
}
});
app.directive('myChild', function () {
return {
require: "^myParent",
restrict: "E",
scope: {
api: '=?'
},
template: "...",
controller: function ( $scope ) {
// I want to access $scope.api in link or controller
},
link: function ($scope, $element, $attr) {
// I want to access $scope.api in link or controller
}
}
});
I can't access $scope.api from the child directive but $scope.parentInstance1 and $scope.parentInstance2 are visible. I realise I can just explicitly declare but I'd rather understand how to do it correctly.
I dont know why you are referencing parentInstance1 and parentInstance2 on my-parent but the attributes on my-child are in myParent's $scope so you can reference the actual $scope.api object that is on myParent's $scope in the attributes of the my-child directive tag and then reference the name of the attribute in the isolate scope definition of the myChild directive.
<my-child inner-api="api"></my-child>
.. and then in the child directive...
app.directive('myChild', function () {
...
scope: {
innerApi: '=?'
}
...
controller: function($scope) {
$scope.innerApi // <- accessible in the controller
}
Heres a simplified fiddle...
The "require" option does not work if the directive is dynamically created, thus it cannot reference its parents' controllers. How can I make it work?
app.directive('parent', function ($compile) {
return {
controller: function() {},
link: function (scope, el, attrs) {
// "child" is dynamically created
el.append( $compile('<div child>')(scope) );
}
}
})
.directive('child', function () {
return {
require: "?^parent",
link: function(scope, el, attrs, pCtrl) {
// "child" cannot find its parent controller
console.log("pCtrl is undefined: ", pCtrl);
}
}
})
here is a plunker DEMO
you need to add child element to parent element before compiling it.
When directive compiles, its tries to get its parent element. And from parent element it tries to find parent controller. But you are compiling your child directive before appending its element to its parent element.
I have created a plnkr for you. Checkout this
app.directive('parent1', function($compile, $timeout) {
return {
controller: function() {
this.name = 'parent controller 1';
},
link: function(scope, el, attrs) {
// "child1" is dynamically created
var elmChild = angular.element('<div child1>');
el.append(elmChild);
$compile(elmChild)(scope);
}
}
})
Here is a directive that is loading new Template from file:
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html'
}
})
Next HTML-element calls this directive from the other HTML-Template (e.g. xxx.html):
<candidates-filter></candidates-filter>
There is next controller for this parent Template (xxx.html):
app.controller('candidatesController', function($scope, $location ){
$scope.addPeson = function() {
$location.url('/candidate/0');
};
});
Method addPerson() is not accessible inside the Directive's template AAAA.html, because
data-ng-click="addPerson()"
is not working there. How to change the Directive to make addPerson() method available inside the directive's template?
TEMPORARY Solution
I fixed this issue by next solution
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html',
controller: function(){
$('button.add').on('click',function(){
location.hash = '#/candidate/0';
});
}
}
})
If I understand the problem correctly:
You can pass a function into the directive for it to use
<candidates-filter></candidates-filter>
becomes
<candidates-filter add-candidate="addPerson()"></candidates-filter>
and the directive definition changed as follows:
.directive('candidatesFilter', function() {
return {
resctict: 'E',
replace: true,
scope: {
addCandidate: '&addCandidate'
}
templateUrl: 'views/directives/filters/AAAA.html'
link: function(scope, element, attrs) {
scope.someFunctionInDirective = function() {
scope.addCandidate();
}
};
}
})
Alternatively you can call it with the ng-click like normal from the button
Hope this helps clarify it?
My goal is to output a value (from a service) through a element directive so that the html will look like this <msg msg="alertMsg"></msg> and out pops a value from the service.
Here is my code thus far:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: {//something here to pass MsgService to template },
template: 'Message:{{MsgService.getAlertMsg()}}'
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
HTML would parse/compile to...
<msg msg="alertMsg">Message: default</msg>
What other code do I need?
If a service wont work directly, Should I access it through a controller?
app.directive("msg", function() {
return {
restrict: "E",
scope: {
getMsg: '&msg'
},
controller: 'MsgController',
template:'Message:{{getMsg()}}'
};
}]);
app.controller('MsgController', ['MsgService' , function(MsgService){
this.getAlertMsg = function(){
return MsgService.getAlertMsg();
};
}]);
HTML would parse/compile to...
<msg msg="getAlertMsg()">Message: default</msg>
Sorry for any errors in code or function use, I'm fairly new to Angular.
You can use the link function of the directive. This function is called once for every rendered instance of your directive. It receives, among other things, the scope of your directive. You can extend your scope very easily with the result of calling the MsgSevice.getAlertMsg() service method:
var app = angular.module("app", []);
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: true,
template: 'Message:{{msg}}',
link: function (scope, $element, attrs) {
scope.msg = MsgService.getAlertMsg();
}
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
Later on, I presume you will want to just display the alert message from the msg DOM attribute of the msg directive. Achieving this is much more simple, since AngularJS is already prepared for this common use case. The solution involves creating an isolate scope. The isolate scope can be populated with properties from the parent environment. One possibility is to use the value of a DOM attribute from your directive's element using the "#" syntax. In this case you won't even need the entire MsgService service:
app.directive("msg", function () {
return {
restrict: "E",
scope: {
"msg": "#"
},
template: 'Message:{{msg}}'
};
});
Simplest would be to set the service on your scope and use that in your template:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: { },
template: 'Message:{{MsgService.getAlertMsg()}}',
link: function(scope, element, attrs) {
scope.MsgService = MsgService;
}
};
}]);
I would like to access a parent directive's scope, but I can't seem to get the right combination of settings. Is this possible and is it the right approach?
I really want to avoid putting something like SOME_CONST (which would help me make DOM updates through control flow) in MyCtrl
<div ng-controller="MyCtrl">
<parent>
<child></child>
</parent>
</div>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.obj = {prop:'foo'};
}
myApp.directive('parent', function() {
return {
scope: true,
transclude: true,
restrict: 'EA',
template: '<div ng-transclude><h1>I\'m parent {{obj.prop}}<h1></div>',
link: function(scope, elem, attrs) {
scope.SOME_CONST = 'someConst';
}
}
});
myApp.directive('child', function() {
return {
restrict: 'EA',
template: '<h1>I\'m child.... I want to access my parent\'s stuff, but I can\'t. I can access MyCtrlScope though, see <b>{{obj.prop}}</b></h1> how can I access the <b>SOME_CONST</b> value in my parent\'s link function? is this even a good idea? {{SOME_CONST}}. I really don\'t want to put everything inside the MyCtrl',
}
});
Please see this fiddle
Thanks
With transclude: true and scope: true, the parent directive creates two new scopes:
Scope 004 is a result of scope: true, and scope 005 is a result of transclude: true. Since the child directive does not create a new scope, it uses transcluded scope 005. As you can see from the diagram there is no path from scope 005 to scope 004 (except via private property $$prevSibling, which goes in the opposite direction of $$nextSibling -- but don't use those.)
#joakimbl's solution is probably best here, although I think it is more common to define an API on the parent directive's controller, rather than defining properties on this:
controller: function($scope) {
$scope.SOME_CONST = 'someConst';
this.getConst = function() {
return $scope.SOME_CONST;
}
}
Then in the child directive:
link:function(scope,element,attrs,parentCtrl){
scope.SOME_CONST = parentCtrl.getConst();
},
This is how the tabs and pane directives work on Angular's home page ("Create Components" example).
Normally the way you access a parent scope variable in a directive is through bi-directional binding (scope:{model:'=model'} - see the angular guide on directives) in the directive configuration), but since you're using transclusion this is not so straight forward. If the child directive will always be a child of the parent directive you can however configure it to require the parent, and then get access to the parent controller in the child link function:
myApp.directive('parent', function() {
return {
scope: true,
transclude: true,
restrict: 'EA',
template: '<div ng-transclude><h1>I\'m parent {{obj.prop}}<h1></div>',
controller: function($scope) {
$scope.SOME_CONST = 'someConst';
this.SOME_CONST = $scope.SOME_CONST;
}
}
});
myApp.directive('child', function() {
return {
restrict: 'EA',
require:'^parent',
scope:true,
link:function(scope,element,attrs,parentCtrl){
scope.SOME_CONST = parentCtrl.SOME_CONST;
},
template: '<h1>I\'m child.... I want to access my parent\'s stuff, but I can\'t. I can access MyCtrlScope though, see <b>{{obj.prop}}</b></h1> how can I access the <b>SOME_CONST</b> value in my parent\'s link function? is this even a good idea? {{SOME_CONST}}. I really don\'t want to put everything inside the MyCtrl',
}
});
See this update: http://jsfiddle.net/uN2uv/
I just had the same problem and finally solved it with the angular manual ;)
In short: you need to use a controller in your parent directive and require that controller in your child directive. This way you are able to get your parent properties.
See https://docs.angularjs.org/guide/directive
Chapter: Creating Directives that Communicate
I changed your fiddle to use a controller, now you can access your constant:
https://jsfiddle.net/bbrqdmt3/1/
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.obj = {prop:'foo'};
}
myApp.directive('parent', function() {
return {
scope: true,
transclude: true,
restrict: 'EA',
template: '<div ng-transclude><h1>I\'m parent {{obj.prop}}<h1></div>',
controller: function($scope) {
this.getConst= function() {
return 'someConst';
}
},
}
});
myApp.directive('child', function() {
return {
restrict: 'EA',
require : '^parent',
link: function(scope, element, attrs, ctrl) {
scope.value= ctrl.getConst();
},
template: '<h1>I\'m child.... I want to access my parent\'s stuff, but I can\'t. I can access MyCtrlScope though, see <b>{{obj.prop}}</b></h1> how can I access the <b>SOME_CONST</b> value in my parent\'s link function? is this even a good idea? {{value}}. I really don\'t want to put everything inside the MyCtrl',
}
});
There's a transclude fn in the arguments of the link fn after the controller.
myApp.directive('parent', function() {
return {
scope: true,
transclude: true,
restrict: 'EA',
template: '<div><h1>I'm a parent header.</h1></div>',
link: function (scope, el, attrs, ctrl, transclude) {
transclude(scope, function (clone, scope) {
element.append(clone); // <-- will transclude it's own scope
});
},
controller: function($scope) {
$scope.parent = {
binding: 'I\'m a parent binding'
};
}
}
});
myApp.directive('child', function() {
return {
restrict: 'EA',
require:'^parent',
scope:true,
link:function(scope,element,attrs,parentCtrl){
},
template: '<div>{{parent.binding}}</div>' // <-- has access to parent's scope
}
});