AngularJS - dynamically created directive's "require" not working - angularjs

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);
}
}
})

Related

Angularjs directive set scope var in directive and access in controller

Hi I am working on angularjs. I am facing an issue in directive.
I have set the scope.user.name="amin shah" on link/click event
and want to access this in controller how is this possible?
var dataSourceDirective = angular.module('mydirective', []);
dataSourceDirective.directive('dir', function () {
return {
restrict: 'C',
scope: true,
link: function ($scope, element, attrs) {
element.bind('click', function () {
$scope.user.name ="amin shah";
$scope.$apply();
$('.sourceType_panel').hide();
$('#sourceType_1_panel').show();
});
}
}
});
controller code
$scope.demo = function () {
console.log($scope.user);`
},
You need to create Isolated scope in your directive.
The given controller should be parent of this directive.
var dataSourceDirective = angular.module('mydirective', []);
dataSourceDirective.directive('dir', function () {
return {
restrict: 'C',
scope: {user:"=user"},
link: function ($scope, element, attrs) {
element.bind('click', function () {
$scope.user.name ="amin shah";
});
}
}
});
In html :
<div ng-copntroller='yourCtrl'>
<dir user="user"></dir>
</div>
In Controller you should initialize the user.
OR
you use $broadcast & $emit if the parent is controller.
Withing link function of directive you can use $rootScope.$emit('user_name_update',user);
And in the controller you can listen this event
$scope.$on('user_name_update',function(data){
console.log(user) // its should give your updated `user` object
})
First of all you should correct your link method and I think you shouldn't need child sope at there. So you should delete your scope bind in directive too. You can reach parent scope with link method.
app.directive('dir', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.user.name ="amin shah";
scope.$apply();
});
}
}
});
and in your controller you can define scope variable like that:
app.controller('MainCtrl', function($scope) {
$scope.user = {
name: ''
}
});
also you should add this directive to HTML :
<dir>Element</dir>
<p>{{user.name}}</p>
here is the working plunkr you should click Element than you can see your name from directive but in parent scope
https://plnkr.co/edit/umTdfukZ22hARoLjxdL3?p=preview

Why is a parent controller's $scope not accessible from a (child) directive?

From reading Scopes (Part 2 of the AngularJS - from beginner to expert in 7 steps series): A $scope can contain both data and functions available in a view. If AngularJS cannot find a function on a local $scope, the containing (parent) $scope will be checked for the property or method there.
Given my implementation of a directive's compile function (based on Angularjs: understanding a recursive directive):
compile: function(tElement, tAttrs) {
var contents = tElement.contents().remove();
console.log(contents);
var compiledContents;
// postLink function.
return {
post: function(scope, iElement, iAttrs) {
if (!compiledContents) {
// Get linking function.
compiledContents = $compile(contents);
}
// Link scope and the template together.
compiledContents(scope, function(clone) {
iElement.append(clone);
});
scope.myEvent = function() {
console.log("My Event handled!");
};
},
pre: function(scope, iElement, iAttrs) { }
}
}
In the code above, I have attached a function to the $scope of the instance element, and this is successfully called from the view. However I expected to be able to move the function definition from the instance element scope and into a parent controller's $scope:
angular.module('Myapp').controller('MyParentController', ['$scope',
function($scope) {
$scope.myEvent = function() {
console.log("My Event handled!");
};
}]);
However the parent controller's function is never called even though it is a parent of the directive for which I provided my own implementation of compile.
Updated to add code for the directive:
angular.module('Myapp').directive("my-directive", function(RecursionHelper) {
return {
restrict: "E",
scope: {
data: '=data'
},
templateUrl: 'view.html',
compile: function(tElement, tAttributes) {
return RecursionHelper.compile(tElement, tAttributes);
}
};
});
..and the RecursionHelper:
angular.module('Myapp').factory('RecursionHelper',
['$compile',
function($compile) {
var RecursionHelper = {
compile: function(tElement, tAttrs) {
var contents = tElement.contents().remove();
var compiledContents;
return {
post: function(scope, iElement, iAttrs) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone) {
iElement.append(clone);
});
},
pre: function(scope, iElement, iAttrs) { }
}
}
}
return RecursionHelper;
}]);
change your scope to
scope: {
data: '=data'
myEvent: '=myEvent'
}
and then on your directive change this
angular.module('Myapp').directive("my-directive",
to
angular.module('Myapp').directive("myDirective",
then pass the function as in
<my-directive data="scope-data" my-event="scope-event-function()"></my-directive>
because your directive has an isolated scope, you can only access data in parent scope.
scope: {
data: '=data'
},

How to force recompile of directive with isolated scope?

There are a few SO posts about refreshing a directive when a (model) value changes:
Angular Directive refresh on parameter change
AngularJS directive gets not updated if scope variable changes
The recommended approach is to watch a value on the scope in the linking function:
link: function(scope, element, attrs) {
scope.$watch("typeId",function(newValue,oldValue) {
// This gets called when data changes.
});
}
Given the following implementation of the recursive directive, <my-directive>:
angular.module('Myapp').directive("MyDirective", function(RecursionHelper) {
return {
restrict: "E",
scope: {
data: '=data'
},
templateUrl: 'view.html',
compile: function(tElement, tAttributes) {
return RecursionHelper.compile(tElement, tAttributes);
}
};
});
...and the RecursionHelper:
angular.module('Myapp').factory('RecursionHelper',
['$compile',
function($compile) {
var RecursionHelper = {
compile: function(tElement, tAttrs) {
var contents = tElement.contents().remove();
var compiledContents;
return {
post: function(scope, iElement, iAttrs) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone) {
iElement.append(clone);
});
},
pre: function(scope, iElement, iAttrs) { }
}
}
}
return RecursionHelper;
}]);
I could add the listener to my linking function:
angular.module('Myapp').factory('RecursionHelper',
['$compile','MyService',
function($compile, MyService) {
var RecursionHelper = {
compile: function(tElement, tAttrs) {
var contents = tElement.contents().remove();
var compiledContents;
return {
post: function(scope, iElement, iAttrs) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone) {
iElement.append(clone);
});
// The listener function.
scope.$watch(MyService.data,function(newValue,oldValue) {
// This gets called when data changes.
});
},
pre: function(scope, iElement, iAttrs) { }
}
}
}
return RecursionHelper;
}]);
I have added a Service, MyService, which contains data that may change and that I'm listening for. The first call to render the directive is started with main.html:
<my-directive data="data"></my-directive>
The directive's compile function is called and ultimately the template, view.html is rendered containing the recursive directive:
<span ng-repeat="item in data.items">
<my-directive data="item"></my-directive>
</span>
Since the <my-directive> data attribute has isolated scope, each nested directive will have its own 'sandboxed' scope.
Questions
My $watch function never gets called even though MyService.data is modified. Is this because the directive has isolated scope?
Assuming my $watch function is called, what is the actual implementation to trigger a recompilation? The SO posts just have comments of console.log statements, but I can't find anything on rendering the entire (root) directive again.

Accessing parent "isolated" scope from child directive

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

Angularjs: set parent directive scope value with child directive

I'm not sure this is the way to do this, but my goal is the following:
I have a parent directive
Inside the parent directive's block, I have a child directive that will get some input from the user
The child directive will set a value in the parent directive's scope
I can take it from there
Of course the problem is that the parent and child directives are siblings. So I don't know how to do this. Note - I do not want to set data in the
Fiddle: http://jsfiddle.net/rrosen326/CZWS4/
html:
<div ng-controller="parentController">
<parent-dir dir-data="display this data">
<child-dir></child-dir>
</parent-dir>
</div>
Javascript
var testapp = angular.module('testapp', []);
testapp.controller('parentController', ['$scope', '$window', function ($scope, $window) {
console.log('parentController scope id = ', $scope.$id);
$scope.ctrl_data = "irrelevant ctrl data";
}]);
testapp.directive('parentDir', function factory() {
return {
restrict: 'ECA',
scope: {
ctrl_data: '#'
},
template: '<div><b>parentDir scope.dirData:</b> {{dirData}} <div class="offset1" ng-transclude></div> </div>',
replace: false,
transclude: true,
link: function (scope, element, attrs) {
scope.dirData = attrs.dirData;
console.log("parent_dir scope: ", scope.$id);
}
};
});
testapp.directive('childDir', function factory() {
return {
restrict: 'ECA',
template: '<h4>Begin child directive</h4><input type="text" ng-model="dirData" /></br><div><b>childDir scope.dirData:</b> {{dirData}}</div>',
replace: false,
transclude: false,
link: function (scope, element, attrs) {
console.log("child_dir scope: ", scope.$id);
scope.dirData = "No, THIS data!"; // default text
}
};
});
If you want that kind of communication, you need to use require in the child directive. That will require the parent controller so you need a controller there with the functionality you want the children directives to use.
For example:
app.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: '<div>{{message}}<span ng-transclude></span></div>',
controller: function($scope) {
$scope.message = "Original parent message"
this.setMessage = function(message) {
$scope.message = message;
}
}
}
});
The controller has a message in the $scope and you have a method to change it.
Why one in $scope and one using this? You can't access the $scope in the child directive, so you need to use this in the function so your child directive will be able to call it.
app.directive('child', function($timeout) {
return {
restrict: 'E',
require: '^parent',
link: function(scope, elem, attrs, parentCtrl) {
$timeout(function() {
parentCtrl.setMessage('I am the child!')
}, 3000)
}
}
})
As you see, the link receives a fourth param with the parentCtrl (or if there is more than one, an array). Here we just wait 3 seconds until we call that method we defined in the parent controller to change its message.
See it live here: http://plnkr.co/edit/72PjQSOlckGyUQnH7zOA?p=preview
First, watch this video. It explains it all.
Basically, you need to require: '^parentDir' and then it will get passed into your link function:
link: function (scope, element, attrs, ParentCtrl) {
ParentCtrl.$scope.something = '';
}

Resources