Angularjs passing object to directive - angularjs

Angular newbie here. I am trying to figure out what's going wrong while passing objects to directives.
here's my directive:
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
and this is the template where I call the directive:
<div walkmap="store.walks"></div>
store.walks is an array of objects.
When I run this, scope.walks logs as undefined while scope logs fine as an Scope and even has a walks child with all the data that I am looking for.
I am not sure what I am doing wrong here because this exact method has worked previously for me.
EDIT:
I've created a plunker with all the required code: http://plnkr.co/edit/uJCxrG
As you can see the {{walks}} is available in the scope but I need to access it in the link function where it is still logging as undefined.

Since you are using $resource to obtain your data, the directive's link function is running before the data is available (because the results from $resource are asynchronous), so the first time in the link function scope.walks will be empty/undefined. Since your directive template contains {{}}s, Angular sets up a $watch on walks, so when the $resource populates the data, the $watch triggers and the display updates. This also explains why you see the walks data in the console -- by the time you click the link to expand the scope, the data is populated.
To solve your issue, in your link function $watch to know when the data is available:
scope.$watch('walks', function(walks) {
console.log(scope.walks, walks);
})
In your production code, just guard against it being undefined:
scope.$watch('walks', function(walks) {
if(walks) { ... }
})
Update: If you are using a version of Angular where $resource supports promises, see also #sawe's answer.

you may also use
scope.walks.$promise.then(function(walks) {
if(walks) {
console.log(walks);
}
});

Another solution would be to add ControllerAs to the directive by which you can access the directive's variables.
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
controllerAs: 'dir',
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
And then, in your view, pass the variable using the controllerAs variable.
<div walkmap="store.walks" ng-init="dir.store.walks"></div>

Try:
<div walk-map="{{store.walks}}"></div>
angular.module('app').directive('walkMap', function($parse) {
return {
link: function(scope, el, attrs) {
console.log($parse(attrs.walkMap)(scope));
}
}
});

your declared $scope.store is not visible from the controller..you declare it inside a function..so it's only visible in the scope of that function, you need declare this outside:
app.controller('MainCtrl', function($scope, $resource, ClientData) {
$scope.store=[]; // <- declared in the "javascript" controller scope
ClientData.get({}, function(clientData) {
self.original = clientData;
$scope.clientData = new ClientData(self.original);
var storeToGet = "150-001 KT";
angular.forEach(clientData.stores, function(store){
if(store.name == storeToGet ) {
$scope.store = store; //declared here it's only visible inside the forEach
}
});
});
});

Related

pass data from controller to directive's link?

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

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

Triggering a function with ngClick within ngTransclude

I have an unordered list loaded with four items from an array while using ngRepeat. The anchor tag in the list item has a function in the ngClick attribute that fires up a message. The function call works well when used like this:
<ul>
<li ng-repeat="n in supsNames">
<a ng-click="myAlert(n.name)">{{n.name}}</a>
</li>
</ul>
I created a simple directive for inserting unordered lists with list items. The list is loaded just fine but the same functioned I previously mentioned does not fire up. The code is as follows:
<div list items="supsNames">
<a ng-click="myAlert({{item.name}})">{{item.name}}</a>
</div>
Here is my javascript and angularjs code:
var app = angular.module('myapp', []);
app.controller('myCtrl', function($scope) {
$scope.title = 'ngClick within ngTransclude';
$scope.supsNames = [
{"name" : "Superman"},
{"name" : "Batman"},
{"name" : "Aquaman"},
{"name" : "Flash"}
];
$scope.myAlert = function(name) {
alert('Hello ' + name + '!');
};
});
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '='
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
console.log(scope);
}
};
});
I also have a plnkr in case you want to see what I tried to do:
http://plnkr.co/edit/ycaAUMggKZEsWaYjeSO9?p=preview
Thanks for any help.
I got the plunkr working. I'm not sure if its exactly what you're looking for. I copied the main code changes below.
Here's the plunkr:
http://plnkr.co/edit/GEiGBIMywkjWAaDMKFNq?p=preview
The modified directive looks like this now:
app.directive('list', function() {
return {
restrict: 'A',
scope: {
items: '=',
ctrlFn: '&' //this function is defined on controller
},
templateUrl: 'list.html',
transclude: true,
link: function(scope, element, attrs, controller) {
//directive fn that calls controller defined function
scope.dirFn = function(param) {
if(scope.ctrlFn && typeof scope.ctrlFn == 'function') { //make sure its a defined function
scope.ctrlFn( {'name': param} ); //not sure why param has to be passed this way
}
}
}
};
});
And here's how it's called in the html file that's bound to your controller:
<div list items="supsNames" ctrl-fn="myAlert(name)">
<a ng-click="dirFn(item.name)">{{item.name}}</a>
</div>
I think what was happening before is that you were trying to use a function defined in your controller within the isolated scope of the directive, so it wasn't working--that function was undefined in the directive. So what I did was added another parameter to the directive that accepts method binding (I think that's what its called) with the '&'.
So basically you pass your controller method to the directive, and that method gets invoked however you want by the directive defined method I creatively named "dirFn". I don't know if this is the best way per se, but I've used it in an existing project with good results ;)
you need to pass the function to the directive
scope: {
items: '=', 'myAlert': '='
},
The ng-repeat inside the template of the directive insert a new scope and it require to call transclude funcion manually to work. I suggest remove ng-repeat and make the transclusion manually passing a copy of the controller scope and setting the item on each copy:
for(var i=0,len=scope.items.length;i<len;i++){
var item=scope.items[i];
var itemScope=scope.$parent.$new();
$transcludeFn(itemScope, function (clone,scope) {
// be sure elements are inserted
// into html before linking
scope.item=item;
element.after(clone);
});
};
I edit the pluker and I hope that could be helpfull: http://plnkr.co/edit/97ueb8SFj3Ljyvx1a8U1?p=preview
For more info about transclusion see: Transclusion: $transcludeFn

Bind an AngularJS directive to a mapping of an array

I have a directive that takes an array of objects. When declaring the directive in markup, the scope has an array of objects that wrap the ones needed by the directive. So I need to apply a map function on the array. What's the right way to do this so updates made to the original array are reflected inside the directive?
Here's a Plunker with a naive approach (which I was surprised to see mostly "work" except for lots of $digest errors): http://plnkr.co/edit/GUCZ3c
You should avoid calling a function from an Angular expression unless, among other things, that function does some very lightweight work (a quick computation, for instance). This question has more details on that matter.
In your case, you should cache the name list and bind it to the directive. Here's an example:
app.controller('MainCtrl', function($scope) {
$scope.people = [
{ name: 'Alice'},
{ name: 'Bob' },
{ name: 'Chuck' }
];
$scope.addName = function(name) {
$scope.people.push({name: name});
};
$scope.$watch(function() { return $scope.people.length; }, function() {
$scope.names = $scope.people.map(function(p) { return p.name; });
});
});
Your directive code remains the same. Here's a fork of your Plunker.
Update: I've changed the code so it uses a $watch to update the name list automatically, following #KrisBraun advice.
I believe you need to treat expressions a little differently when binding them to your isolated scope. Here is a forked plunker.
app.directive('myDirective', function($compile) {
return {
restrict: 'E',
scope: {
names: '&namesFunc'
},
template: '<div><p>JSON: {{names() | json}}</p><p>Names:</p><ul><li ng-repeat="name in names()">{{name}}</li></ul></div>',
replace: true,
link: function(scope, elem, attr, ctrl) {
}
};
});

Angular Directive Different Template

I have a directive myDirective with variable type. If I run <my-directive type="X"> I want the directive to use templateUrl: x-template.html.
If I do <my-directive type="Y"> I want the directive to use templateUrl: y-template.html.
This is my current directive.
app.directive('myDirective', function() {
var myDirective = {
templateUrl: 'X-template.html',
restrict: 'E',
scope: {
type: '='
},
};
return myDirective;
});
I read thru stackoverflow and angular documentation but have not found anything that I need.
I am now trying to do something along the lines of:
if ($scope.type === 'X') {
templateUrl: 'X-template.html',
}
else if ($scope.type === 'Y') {
templateUrl: 'Y-template.html',
}
But do not know where to do it.
Do you guys know if this is possible and how?
Angular will accept a function as the template option, so you could do something like so:
.directive('myDirective', function () {
return {
templateUrl: function (tElement, tAttrs) {
if (tAttrs) {
if (tAttrs.type === 'X') {
return 'X-template.html';
}
if (tAttrs.type === 'Y') {
return 'Y-template.html';
}
}
}
}
});
For more info, see the documentation for the $compile service.
You can work around this issue using ng-include inside compile:
app.directive('myDirective', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
element.append('<div ng-include="\'' + attrs.type + '-template.html\'"></div>');
}
}
});
fiddle
If you're willing to live on the bleeding edge with a build on the 1.1.x code path (note the warning attached to every 1.1.x build notes entry so I don't dilute this answer by repeating it again here), you're in luck--this very feature was just added in the 1.1.4 release on April 3rd. You can find the release notes for 1.1.4 here and the feature's task log includes a Jasmine test that demonstrates how to use the new functionality.
If you're more conservative and are using a 1.0.x release, then you won't be able to accomplish this as easily, but it can be done. Mark Rajcok's solution looks like it would fit your requirements as-stated, but I would just add a few additional notes:
Aside from its 1.1.4 release, compile-time directives don't support modification at runtime.
As of 1.1.4, you can safely modify the attributes of compile-time directives, but only from another compile-time directive.
You may want to consider replaceWith() instead of append() since <my-directive> is not a standard-defined HTML element type.
If your X and Y templates contain additional directives, I don't think you'll be able to pass attributes on <my-template> through to the root element of your template so easily.
A directive with replace: true will transfer attributes from the source element to its replacement root, but I do not think that ngInclude will do the same from is host to the root of the included template.
I also seem to recall that ngInclude does not require that its template have exactly one root element.
You could perhaps preserve attributes on a replacement parent by using replaceWith() instead of append() and wrapping the <div ng-include=""> tag within a <div></div>. The outer <div> could hold attributes and would still be accessible after the <div ngInclude> element replaced itself with loaded content.
Be aware that ngInclude creates a new scope. This subjects you to a flashing yellow klaxons warning about the dangers of primitive scope models. For more information, see this fine page from Angular's GitHub depot.
I can propose another alternative for those on 1.0.x, but it involves a fair amount of code. It's a more heavy-weight operation, but it has the upside of not only being able of switching between templates, but full-fledged directives as well. Furthermore, its behavior is more readily dynamic.
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'partials/directive/my-directive.html',
link: function(scope, element, attrs, ctrl) {
// You can do this with isolated scope as well of course.
scope.type = attrs.type;
}
}
);
my-directive.js
<div ng-switch on="{{type}}">
<div ng-switch-where="X" ng-include="X-template.html"></div>
<div ng-switch-where="Y" ng-include="Y-template.html"></div>
</div>
my-directive.html
This is my version for optionally overriding a default template
templateUrl: function (elem, attrs) {
if (attrs.customTemplate) {
return '/path/to/components/tmpl/' + attrs.customTemplate + '.html';
} else {
return '/path/to/components/tmpl/directive.html';
}
}
e.g on a directive
<div my-directive custom-template="custom"></div>
I solve this problem so:
app.directive("post", function ($templateCache, $compile) {
function getTemplate(mode) {
switch (mode) {
case "create":
return "createPost.html";
case "view":
return "viewPost.html";
case "delete":
return "deletePost.html";
}
}
var defaultMode = "view";
return {
scope: {},
restrict: "AE",
compile: function (elem, attrs, transclude) {
return function ($scope, $element, $attr) {
function updateTemplate() {
$element.html("");
$compile($templateCache.get(getTemplate($scope.mode)).trim())($scope, function (clonedElement, scope) {
clonedElement.appendTo($element);
});
}
$scope.mode = $attr.mode || defaultMode;
$scope.$watch("mode", updateTemplate);
}
}
}
});
It's probably not the best way to do this, but I have no extra scope.
Ok, this might help someone here :-)
To inject your custom attr into your link or controller function use the following.
I'm at work right now but will post a fiddle later if I get a chance :-)
.directive('yourDirective', function() {
return {
restrict: 'EA',
template: '<div></div>', // or use templateUrl with/without function
scope: {
myAttibute: '#myAttr' // adds myAttribute to the scope
},
link: function(scope) {
console.log(scope.myAttibute);
}
}
}
// HTML ""
// Console will output "foo"

Resources