I'm trying to access a variable of a controller from a directive but the variable is undefined and I can't figure out why. The variable is undefined immediately on page load which is I'd guess is because the html is loaded before the controller is initialised but I don't understand how it's undefined afterwards when the html is loaded and renderTimetable is called.
angular.module('app')
.directive('timetable',
function() {
return {
restrict: 'E',
scope: {
bookings: '=',
timetableDate: '='
},
link: function(scope, elem, attrs) {
scope.$watch('bookings', function(updatedBookings, unupdatedBookings) {
if (typeof updatedBookings !== 'undefined') {
console.log(scope.timetableDate); // undefined
scope.renderTimetable(updatedBookings);
}
});
// bookings
scope.renderTimetable = function(planes) {
console.log(scope.timetableDate); // undefined
}
};
}
};
});
And the directive has attribute set with controller's scope:
<timetable bookings="bookings" timetableDate="timetableDate"></timetable>
The timetableDate model binded like so:
<input readonly datepicker ng-model="timetableDate" value="timetableDate" />
In the controller the variable is set immediately, console.log gets a date string as expected:
...
function($scope, $http, $location, $interval) {
$scope.bookingModalVisible = false;
$scope.timetableDate = new Date().toDateString();
console.log(timetableDate); // NOT undefined
Change
<timetable bookings="bookings" timetableDate="timetableDate"></timetable>
to
<timetable bookings="bookings" timetable-date="timetableDate"></timetable>
From Angular docs:
Angular normalizes an element's tag and attribute name to determine
which elements match which directives. We typically refer to
directives by their case-sensitive camelCase normalized name (e.g.
ngModel). However, since HTML is case-insensitive, we refer to
directives in the DOM by lower-case forms, typically using
dash-delimited attributes on DOM elements (e.g. ng-model).
Related
I'm calling a controller function from directive but the function parameter returns undefined when I console.log to check the value. Wondering what I'm doing wrong or maybe a step I forgot. I actually hard coded a value to see if this shows but only get undefined in the console. NOTE: The custom directive template is coming from external file so the function parameter is not being past to the controller. It only works if the custom directive element has the value attached. Should work with the inside directive html.
//******************** Directive ********************//
app.directive('customdir', [function() {
return {
restrict: "E",
template : "<div>Get product<button ng-click="addToCart(56)">Add to Cart</button></div>",
scope: {
addToCart:"&"
},
link: function(scope, el, attrs) {
}
};
}]);
//******************** Controller ********************//
app.controller('mycontroller', function($scope) {
$scope.addToCart = function(thumbProductId){
$scope.thumbProductId = thumbProductId;
console.log("thumbProductId =" + $scope.thumbProductId); // Returns Undefined
};
});
//******************** Html ********************//
<html>
<div ng-controller="mycontroller">
<custom-dir add-to-cart="addToCart(thumbProductId)"> </custom-dir>
</div>
</html>
There were a couple things wrong in the code, the first being "customdir" not having a "-" between it, as there's no capital. You also need to escape certain characters in your html, such as quotations and forward slashes. Here's a plunkr of your example:
http://plnkr.co/edit/FYUGBfIPtrl6Q7GWd597?p=preview
And the directive now looks:
myApp.directive('customdir', [function() {
return {
restrict: "E",
template : "<button ng-click=\"addToCart(thumbProductId)\">Add to Cart<\/button>",
scope: {
addToCart: "&"
}
};
}]);
Exposing Local Values to Parent Scope with Directive Expression Binding
To use Expression Binding to pass data from directive scope to parent scope, invoke the expression with a locals object.
myApp.directive('customdir', function() {
return {
restrict: "E",
template : "<button ng-click='addToCart({$id: 56})'>Add 56 to Cart</button>",
scope: {
addToCart: "&"
}
};
});
The above directive invokes the Angular Expression defined by the add-to-cart attribute with the value 56 exposed as $id.
The HTML:
<div ng-controller="mycontroller">
<customdir add-to-cart="thumbProductId = $id"> </customdir>
ThumbProductId => {{thumbProductId}}
</div>
When the user clicks on the button in the customdir element, the Angular Expression invoked will set thumbProductId to the value exposed by $id which in this case is 56.
To invoke a parent function with a local, simply make the Angular Expression a function:
<customdir add-to-cart="parentFn($id)"> </customdir>
The DEMO on PLNKR.
In my controller :
myApp.controller('homeCtrl', function($scope, $rootScope, $state, 'red';
$rootScope.$on('new_story', function(event, data) {
$scope.cardObj = {key:'value'};
});
});
In my HTML :
<div clickmeee ></div>
<div id="feedContainer" card='{{cardObj}}'> </div>
In my directive :
myApp.directive('clickmeee', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.$watch('card', function(newVal, oldVal) {
alert(scope.card);
});
});
}
};
});
How do I pass data from controller to this directive. I compile some html and prepend it to the div. All of that is sorted out but I need some data from object I am trying to pass.
Any help??
There are several problems in your code:
you define a scope attribute named 'card', but you use cardObj instead
you use a watch that is completely unnecessary. And worse: you create a new watch every time the element is clicked
you don't define any card attribute on your clickmeee element. Instead, you're placing it on another element, on which the directive is not applied
you're passing the attribute with '#'. That works, but the directive will receive a string, containing the JSONified object, rather than the object itself
you're not showming us where you emit an event that will initialize cardObj in the controller scope
Here is a plunkr showing a working version of your code.
Also, note that using bind('click') is a bad idea. You'd better have a template in your directive and use ng-click in the template, or simply not use a directive at all and just use ng-click directly on the div element.
Bad news. You are doing it wrong all the ways.
Firstly
card='{{cardObj}}' >
this one should be put in the
<div clickmeee ></div>
So you can take it as binded scope variable in your directive registration
Secondly
If you managed to use '#' syntax
card: '#'
it will turn your input to string, not a binded scope. Use '=' instead.
In the end
You dont need to use watch here:
scope.$watch('card', function(newVal, oldVal) {
alert(newVal);
});
since scope.card is binded via '=' connector. Just simple use alert(scope.card). (Need to warn you that alert an object is not a good idea)
I have tried your code here: plunker. Changed a litte bit by using cardObj as string for easier presentation. Does it match your work?
You should watch the card object:
myApp.directive('clickmeee', function() {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
scope.$watch('card', function(value) {
console.log(value);
});
}
};
});
And:
<div clickmeee id="feedContainer" card='{{cardObj}}'> </div>
Whenever the controller changes the cardObj, the directive's watch on card is triggered:
$scope.$apply(function() {
$scope.cardObj = "test";
}
I have passed an object from controller to directive but when i am reading object in directive i am not able to, it seems in directive object is being read as string.Code is below , i wane to read City and State from the object.
Html File
<div ng-controller="WeatherController">
<div weather ng-object="{{Object}}"></div>
</div>
Controller
.controller('WeatherController', ['$scope', function ($scope) {
$scope.webpartData.OverviewData.Person.Address.City;
$scope.Object = {
City: '',
State: ''
};
$scope.Object.City = 'TestCity';
$scope.Object.State = 'TestState';
});
})
}])
Directive
angular.module('WeatherModule', [])
.directive('Weather', ["$timeout", function($timeout) {
return {
restrict: 'EA',
template: '<div id="weather"></div>',
scope: {
ngObject: '#ngObject'
},
link: function(scope, element, attrs) {
scope.$watch('ngObject', function(value) {
scope.ngObject = value;
});
$timeout(function() {
console.log('Location' + scope.Object.City + ',' + scope.Object.State);
}, 100);
}
};
}])
There are differences between #, = and & in referencing the directive scope members.
1. "#" (Text binding / one-way binding)
2. "=" (Direct model binding / two-way binding)
3. "&" (Behaviour binding / Method binding)
# means that the changes from the controller scope will be reflected in the directive scope but if you modify the value in the directive scope, the controller scope variable will not get affected.
# always expects the mapped attribute to be an expression. This is very important; because to make the “#” prefix work, we need to wrap the attribute value inside {{}}.
= is birectional so if you change the variable in directive scope, the controller scope variable gets affected as well
& is used to bind controller scope method so that if needed we can call it from the directive
In your case you may need to use = instead of #
Have a look on the following fiddle (It's not mine, but it have good and to the point illustration) http://jsfiddle.net/maxisam/QrCXh/
Some Related Questions also:
What is the difference between '#' and '=' in directive scope in AngularJS?
What is the difference between & vs # and = in angularJS
You should use "=" instead "#". And ng-object="Object" instead ng-object="{{Object}}".
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)
}
});
}
};
}
])
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.