Not able to access angular directive's isolated scope - angularjs

First Directive:
app.directive("myDirectiveOne", function($rootScope){
return {
templateUrl : "/custom-one-html.html",
restrict: "AE",
replace:true,
scope: {
somedata: "=",
flags: "=",
functionone: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.firstFunction = function(){
console.log("First function is getting called")
}
$scope.$on('firstBroadcast',function(event, data){
$rootScope.$broadcast('secondBroadcast', data)
});
}
Second Directive:
app.directive("myDirectiveTwo", function($rootScope){
return {
templateUrl : "/custom-two-html.html",
restrict: "AE",
replace:true,
scope: {
data: "=",
functiontwo: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.secondFunction = function(){
console.log("Second function is getting called")
$rootScope.$broadcast('firstBroadcast', {})
}
$scope.$on('secondBroadcast',function(event, data){
$scope.callSomeFunctionWithData(data);
});
$scope.secondFunction();
$scope.editFunction = function(x){
console.log("This is the edit function", x);
}
Parent Controller:
$scope.parentFuntion = function(){
console.log("No trouble in calling this function")
}
So, the problem is when I try calling a function from a myDirectiveTwo html template, the controller which is active is the parent controller and not the isolated one.
May be it has something to do with the broadcasts I am using?
Html Code:
<div ng-repeat="x in data">
<h5>{{x.name}}</h5>
<button ng-click="editFunction(x)">Edit</button>
</div>
The strange thing is I get data values and ng-repeat works fine on load. But, when I click on the button it doesnt do anything. And if I add the same function in the parent controller, it gets called.. :(
How do I make the isolated scope controller active again..?

The problem is that ng-repeat creates a child scope, therefore the editFunction ends up being on the parent scope.
From docs
... Each template instance gets its own scope, where the given loop variable is set to the current collection item ...
Docs here
You can verify that this is the issue by getting your button element's scope and checking the $parent, as such angular.element(document.getElementsByTagName("button")).scope()
Although considered code smell, you can append $parent to your function call in order to access it, though keep in mind this now places a dependency on your HTML structure.
<button ng-click="$parent.editFunction(x)">Edit</button>

The issue was that I was using a deprecated method replace:true. This caused the unexpected scenarios.. As #Protozoid suggested, I looked at his link and found the issue.. To quote from the official documentation:
When the replace template has a directive at the root node that uses transclude: element, e.g. ngIf or ngRepeat, the DOM structure or scope inheritance can be incorrect. See the following issues: Incorrect scope on replaced element: #9837 Different DOM between template and templateUrl: #10612
I removed replace:true and its fine now :)
This is the link:
Here

Related

$scope.$apply() is not updating the parent scope. Is this a binding issue?

Perhaps this is a poor way of doing this, so I'm open to changing this.
I am implementing a drag and drop function through a <drag-item> directive and a <drop-target> directive. The <drop-target> directive successfully calls this function in the parent scope:
$scope.testAddSet = function(){
console.log($scope.workoutItem); // gives me the last value
$scope.thisMonth[0].sets.push($scope.workoutItem);
$scope.$apply(); // needed to update the view, but uses the old value for workoutItem "test"
};
$scope.workoutItem = "test"; // value declared here
but my <drag-item> directive doesn't seem to update the value of $scope.workoutItem,
View:
<div ng-repeat="exercise in exercises">
<drag-item workout-item="workoutItem" exercise="exercise"></drag-item>
</div>
<drag-item> directive:
angular.module('app.directives.dragItem', [])
.directive('dragItem', function(){
return {
restrict: 'E',
scope: {
exercise: '=',
workoutItem: '='
},
templateUrl: "templates/dragTile.html",
link: function(scope, element, attrs){
element.on('dragstart', function (event) {
scope.workoutItem = scope.exercise.name; //correctly updates the local scope, but not the parent scope
console.log(scope.workoutItem);
});
}
};
});
How do I fix this?
Looks like the problem is that you are defining $scope.workoutItem in your parent scope and then directive is creating another workoutItem property at directive's scope level. To avoid this, it is recomended that you use dots(.) to define your models. Please try something like:
$scope.workout = {item: undefined}
and then in your view:
<div ng-repeat="exercise in exercises">
<drag-item workout-item="workout.item" exercise="exercise"></drag-item>
</div>
I think this should solve your problem without modifying the directive itself.

Directive inside templateUrl of another directive and passing attribute

I'm having two directives and I'm trying to call one directive inside another directive's templateUrl with some attribute but I'm not able to get the compiled attribute value in the second directive. Code is like this:
1st directive
app.directive('myDir', function() {
return {
link: function(scope, element, attrs, controller) {
scope.myVal='hello';
},
templateUrl: 'directive1.html',
scope: {}
}
directive.html
<div>
<child-dir attrval="{{myVal}}"></child-dir>
<div>
2nd directive
app.directive('childDir', function() {
return {
templateUrl: template(element,attrs) {
alert(attrs.attrval);
},
scope: {}
}
Here, attrs.attrval is coming like this {{myVal}}.
but I want the value hello. Can anyone help me?
Please note two things here:
1) I'm using templateUrl.
2) I'm passing a scope variable's value as an attribute to the child directive.
I had the same problem and finally found the solution (probably not the best, but it's working for me). Your example is a litte bit different from mine, but I suppose in your example would be:
Parent directive and directive.html equals, but child directive:
app.directive('childDir', function() {
return {
templateUrl: template(element,attrs) {
attrs.$observe("attrval", function(){
alert(attrs.attrval);
});
},
scope: {}
}
When template is set in parent directive the value is not set yet, but child directive is trying to use it. With 'observe', child directive can force refresh when attrval is really set.
If not works for you, tell me and I will post parts of my code in case it would be useful.

Angularjs - Pass argument to directive

Im wondering if there is a way to pass an argument to a directive?
What I want to do is append a directive from the controller like this:
$scope.title = "title";
$scope.title2 = "title2";
angular.element(document.getElementById('wrapper')).append('<directive_name></directive_name>');
Is it possible to pass an argument at the same time so the content of my directive template could be linked to one scope or another?
here is the directive:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
replace:true
};
})
What if I want to use the same directive but with $scope.title2?
You can pass arguments to your custom directive as you do with the builtin Angular-directives - by specifying an attribute on the directive-element:
angular.element(document.getElementById('wrapper'))
.append('<directive-name title="title2"></directive-name>');
What you need to do is define the scope (including the argument(s)/parameter(s)) in the factory function of your directive. In below example the directive takes a title-parameter. You can then use it, for example in the template, using the regular Angular-way: {{title}}
app.directive('directiveName', function(){
return {
restrict:'E',
scope: {
title: '#'
},
template:'<div class="title"><h2>{{title}}</h2></div>'
};
});
Depending on how/what you want to bind, you have different options:
= is two-way binding
# simply reads the value (one-way binding)
& is used to bind functions
In some cases you may want use an "external" name which differs from the "internal" name. With external I mean the attribute name on the directive-element and with internal I mean the name of the variable which is used within the directive's scope.
For example if we look at above directive, you might not want to specify another, additional attribute for the title, even though you internally want to work with a title-property. Instead you want to use your directive as follows:
<directive-name="title2"></directive-name>
This can be achieved by specifying a name behind the above mentioned option in the scope definition:
scope: {
title: '#directiveName'
}
Please also note following things:
The HTML5-specification says that custom attributes (this is basically what is all over the place in Angular applications) should be prefixed with data-. Angular supports this by stripping the data--prefix from any attributes. So in above example you could specify the attribute on the element (data-title="title2") and internally everything would be the same.
Attributes on elements are always in the form of <div data-my-attribute="..." /> while in code (e.g. properties on scope object) they are in the form of myAttribute. I lost lots of time before I realized this.
For another approach to exchanging/sharing data between different Angular components (controllers, directives), you might want to have a look at services or directive controllers.
You can find more information on the Angular homepage (directives)
Here is how I solved my problem:
Directive
app.directive("directive_name", function(){
return {
restrict: 'E',
transclude: true,
template: function(elem, attr){
return '<div><h2>{{'+attr.scope+'}}</h2></div>';
},
replace: true
};
})
Controller
$scope.building = function(data){
var chart = angular.element(document.createElement('directive_name'));
chart.attr('scope', data);
$compile(chart)($scope);
angular.element(document.getElementById('wrapper')).append(chart);
}
I now can use different scopes through the same directive and append them dynamically.
You can try like below:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
scope:{
accept:"="
},
replace:true
};
})
it sets up a two-way binding between the value of the 'accept' attribute and the parent scope.
And also you can set two way data binding with property: '='
For example, if you want both key and value bound to the local scope you would do:
scope:{
key:'=',
value:'='
},
For more info,
https://docs.angularjs.org/guide/directive
So, if you want to pass an argument from controller to directive, then refer this below fiddle
http://jsfiddle.net/jaimem/y85Ft/7/
Hope it helps..
Controller code
myApp.controller('mainController', ['$scope', '$log', function($scope, $log) {
$scope.person = {
name:"sangeetha PH",
address:"first Block"
}
}]);
Directive Code
myApp.directive('searchResult',function(){
return{
restrict:'AECM',
templateUrl:'directives/search.html',
replace: true,
scope:{
personName:"#",
personAddress:"#"
}
}
});
USAGE
File :directives/search.html
content:
<h1>{{personName}} </h1>
<h2>{{personAddress}}</h2>
the File where we use directive
<search-result person-name="{{person.name}}" person-address="{{person.address}}"></search-result>
<button my-directive="push">Push to Go</button>
app.directive("myDirective", function() {
return {
restrict : "A",
link: function(scope, elm, attrs) {
elm.bind('click', function(event) {
alert("You pressed button: " + event.target.getAttribute('my-directive'));
});
}
};
});
here is what I did
I'm using directive as html attribute and I passed parameter as following in my HTML file. my-directive="push" And from the directive I retrieved it from the Mouse-click event object. event.target.getAttribute('my-directive').
Insert the var msg in the click event with scope.$apply to make the changes to the confirm, based on your controller changes to the variables shown in ng-confirm-click therein.
<button type="button" class="btn" ng-confirm-click="You are about to send {{quantity}} of {{thing}} selected? Confirm with OK" confirmed-click="youraction(id)" aria-describedby="passwordHelpBlock">Send</button>
app.directive('ngConfirmClick', [
function() {
return {
link: function(scope, element, attr) {
var clickAction = attr.confirmedClick;
element.on('click', function(event) {
var msg = attr.ngConfirmClick || "Are you sure? Click OK to confirm.";
if (window.confirm(msg)) {
scope.$apply(clickAction)
}
});
}
};
}
])

Passing controller scope from directive to partial

I am new to AngularJs. I have a route configured to a controller and a template. In the template I am calling a custom directive. The custom directive loads a partial file in which I need to fetch the scope which is set by the controller. How can I pass the scope from the directive to the partial so that the partial file can consume the scope data.
Kindly let me know how to get this implemented in AngularJs
Code snippet inside the link function of the directive:
myApp.directive('basicSummary', function() {
return {
restrict: 'E',
replace:'true',
templateUrl: "partials/basicSummary.html",
link: function(scope, elem, attrs) {
console.log(scope.testURL);
}
}
});
Output on the console is : undefined
Update: I found the root cause of why the variable was not getting initialized. The issue, is that the variable is being fetched by making an ajax call in the controller and by the time the ajax call is completed and the variable is put inside the scope inside the controller, the partial file is already loaded and hence I am getting the value of the variable inside the directive as undefined.
How can I make sure that only on success of the http call, I load the partial and the directive?
Adding the jsfiddle link: http://jsfiddle.net/prashdeep/VKkGz/
You could add a new variable to your scope in the definition of your directive to create a two-way binding, that you could safely watch for changes (for use in Javascript once the variable has been populated via ajax), and in your template use ng-show to show/hide based on whether or not the variable is defined. See this JSFiddle for an example of how that would work: http://jsfiddle.net/HB7LU/3588/
Default Template
<div ng-controller="MyCtrl">
<my-test my-test-url="myAjaxProperty"></my-test>
</div>
App Definition
var myApp = angular.module('myApp',[]);
myApp.directive('myTest', function(){
return {
restrict: 'E',
repalce: 'true',
template: '<div ng-show="myTestUrl">{{myTestUrl}}</div>',
scope: { myTestUrl: '=' },
link: function(scope, elem, attrs){
scope.$watch('myTestUrl', function(newVal, oldVal){
if(newVal){
console.log("Watched value is defined as: "+scope.myTestUrl);
}
})
}
}
});
function MyCtrl($scope, $timeout) {
$timeout(function(){
$scope.myAjaxProperty = "My Test Url";
console.log("Ajax returned");
}, 3000, true)
console.log("Default Controller Initialized");
}
as long as you don't isolate your scope with,
scope: {}
in your directive, your scope has access to its parent controller's scope directly.

AngularJS directive transclude part binding

I'd like to use a directive, transclude content, and call directive's controller method within the transcluded part:
<mydirective>
<div ng-click='foo()'>
click me
</div>
</mydirective>
app.directive "mydirective", ->
return {
restrict: 'EACM',
transclude: true
template: "<div ng-transclude></div>"
scope: { } #required: I use two way binding on some variable, but it's not the question here
controller: [ '$scope', ($scope)->
$scope.foo = -> console.log('foo')
]
}
plunkr here.
How can I do that please?
I have a different answer, which is not a hack and I hope it will be accepted..
see my plunkr for a live demo
Here is my usage of the directive
<div custom-directive custom-name="{{name}}">
if transclude works fine you should see my name right here.. [{{customName}}]
</div>
Note I am using customName within the directive and I assign it a value as part of the directive's scope.
Here is my directive definition
angular.module('guy').directive('customDirective', function($compile, $timeout){
return {
template : '<div class="custom-template">This is custom template with [{{customName}}]. below should be appended content with binding to isolated scope using the transclude function.. wait 2 seconds to see that binding works</div>',
restrict: 'AC',
transclude: true,
scope : {
customName : '#'
},
link : function postLink( scope, element, attrs, dummy, transcludeFn ){
transcludeFn( scope, function(clone, innerScope ){
var compiled = $compile(clone)(scope);
element.append(compiled);
});
$timeout( function(){
scope.customName = 'this stuff works!!!';
}, 2000);
}
}
});
Note that I am changing the value on the scope after 2 seconds so it shows the binding works.
After reading a lot online, I understood the following:
the ng-transclude directive is the default implementation to transclusion which can be redefined per use-case by the user
redefining a transclusion means angular will use your definition on each $digest
by default - the transclusion creates a new scope which is not a child of the isolated scope, but rather a sibling (and so the hack works). If you redefine the transclusion process you can choose which scope is used while compiling the transcluded content.. -- even though a new scope is STILL created it seems
There is not enough documentation to the transclude function. I didn't even find it in the documentation. I found it in another SO answer
This is a bit tricky. The transcluded scope is not the child of the directive scope, instead they are siblings. So in order to access foo from the ng-click of the transcluded element, you have to assign foo to the correct scope, i.e. the sibling of the directive scope. Be sure to access the transcluded scope from the link function because it hasn't been created in controller function.
Demo link
var app = angular.module('plunker', []);
app.directive("mydirective", function(){
return {
transclude: true,
restrict: 'EACM',
template: "<div> {{ name }} <br/><br/> <div ng-transclude> </div></div>",
scope: { },
link: function($scope){
$scope.name = 'Should change if click below works';
$scope.$$nextSibling.foo = function(){
console.log('foo');
$scope.name = 'it works!';
}
}
}
})
Another way is assigning foo to the parent scope because both prototypally inherits from the parent scope, i.e.
$scope.$parent.foo = ...
Technically, if you remove scope: { }, then it should work since the directive will not create an isolated scope. (Btw, you need to add restrict: "E", since you use the directive as element)
I think it makes more sense to call actions defined in parent scope from directive rather than call the actions in the directive from parent scope. Directive should be something self-contained and reusable. The actions in the directive should not be accessible from outside.
If you really want to do it, you can try to emit an event by calling $scope.$broadcast(), and add a listener in the directive. Hope it helps.

Resources