Custom directive scope vs attrs - javascript

I have one concern when creating a custom directive in angular.
When I'm using a link function, I'm not sure what is the real difference when accessing attributes with attrs or scope.
Take this piece of code for example:
myApp.directive('someDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
title: '=title'
},
template: '<img/>',
link: function(scope, element, attrs) {
if (scope.title) {
// do something here
}
if (attrs.title){
// do something here
}
},
}
From my observations accessing 'title' attribute from attrs and by scope has a similar effect. What is the real difference?

The difference is that attribute is of a String type by definition. Always. In your case attrs.title will be literally string equal to whatever you pass into attribute in HTML.
However, scope.title is parsed and evaluated result of the attribute attr.title.
Ex. If you use something like this in HTML
<some-directive title="name"></some-directive>
where $scope.name = "Thomas Mann" defined in the scope, then attr.title will be string "name", while scope.title will be "Thomas Mann".

Related

Pass JSON object from Angular service to directive

I'm trying to pass a JSON object from an Angular service to a directive. Actually I just want to $compile a directive on-the-go and pass an object to the directive.
It should look something like this:
var template = '<gmap-info-window layer="layer" marker="marker"></gmap-info-window>',
content = $compile(template)(searchScope);
Whereas the directive looks like this:
.directive('gmapInfoWindow', [function() {
scope: {
marker: '=',
layer: '='
},
link: function(scope, element, attrs) {
// access objects in attrs
}
}]);
That doesn't work. All I get in the attrs.marker and attrs.layer is plain strings.
Now what I've tried and accomlished is using the transcludeFn function of the $compile function. It works, but I don't feel it being the right way to do what I'm trying to accomplish.
var template = '<gmap-info-window></gmap-info-window>',
content = $compile(template)(searchScope, null, {
parentBoundTranscludeFn: function() {
return {
marker: _marker,
layer: _layer
};
}
});
Whereas the directive looks like this:
.directive('gmapInfoWindow', [function() {
scope: {},
link: function(scope, element, attrs, controller, transcludeFn) {
var objects = transcludeFn();
// The marker and layer are in objects now!
}
}]);
I can't imagine that there's no other way to do what I wanna do. This looks kinda dirty. Thanks for your insight!
All I get in the attrs.marker and attrs.layer is plain strings.
You need to understand that attribute is always a string by definition. It not possible that you have an object there. What Angular does is it evaluates values of those attributes (strings) in proper context (scope of compilation) according to scope configuration of the directive. Then the result of this evaluation is available in scope object of the link function. This is what you need to use:
link: function(scope, element, attrs) {
console.log(scope.marker, scope.layer);
}

Trying to load variable into Angular directive

So I am so confused! I have HTML that looks like this:
<span paper-embed="" url="theUrl"></span>
and theUrl is a variable that loads a different URL from my ng-controller. Then I have an Angular directive looks like this:
app.directive('paperEmbed', function() {
return {
restrict: 'AEC',
transclude:true,
scope: {
key: '=',
value: '='
},
link: function(scope, element, attrs) {
// is does some jQuery here
}
};
});
my question is, I want to access the URL inside the directive - the variable named theUrl, so how do I do that? I looked up on SA and seemed like
console.log({{theUrl}});
might work but it does not.
Change your scope to
scope: {
key: '=',
value: '=',
url: '='
},
And in your link function you can use it like scop.url
You can access it with attrs.url
Since you are trying to implement a directive with the following:
<span paper-embed="" url="theUrl"></span>
you should use restrict: 'A',
the url attribute can be accessed either through your scope declaration as # or through the attrs object passed into your link function.
as to the console.log({{theUrl}}). {{}} is specifically design to interpolate javascript-keys in the markup, that is processed through the $compile cycle.
Here is something to get you started:
HTML
<span paper-embed url="http://example.com" />
javascript
var app = angular.module('exampleApp', []);
app.directive('paperEmbed', function() {
return function(scope, elem, attrs) {
console.log(attrs.url);
};
});
get a little more detailed information here: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
You can either use attrs.url to access it or you can add the url to the scope:
app.directive('paperEmbed', function() {
return {
restrict: 'AEC',
transclude:true,
scope: {
key: '=',
value: '=',
url: '#'
},
link: function(scope, element, attrs) {
console.log(scope.url);
}
};
Whenever you add a variable to the scope like above, the directive is expecting an html attribute to supply that value. In this case url.
<div paper-embed="obj1" key="obj2" value="obj3" url="obj4"></div>
scope.url === obj4;
Among many others, there are two easy ways to do this:
- First method is to access it using the attrs.
- Second method is to pass the parameters by defining in the scope.
First method is to use attrs. attrs has all the attributes. You can simply access the url in the link function as attrs.url
link:function(scope,element,attrs){
console.log(attrs.url)
}
You will see whatever is placed instead of theUrl is displayed on the console.
The other alternative is to change your directive to the following:
app.directive('paperEmbed', function() {
return {
restrict: 'AEC',
transclude:true,
scope: {
key: '=',
value: '=',
theUrl: '='
},
link: function(scope, element, attrs) {
// is does some jQuery here
}};});
Now you can access it in the link function as scope.url. To set the value to be passed simply set $scope.theUrl='whatever your url is' in your ng-controller.

Why is my angular directive not getting Parent controller scope inside isolated scope?

I've followed Angular docs precisely to get a directive working with an isolated scope containing a couple of vars from the parent Controller's scope object.
app.controller('MainCtrl', function($scope) {
$scope.name = 'Parent Name';
$scope.pokie = {
whyIs: "thisUndefined?"
};
});
app.directive('parseObject', function() {
var preLink = function($scope, el, att, controller) {
console.log('[link] :: ', $scope);
};
var postLink = function($scope, el, att, controller) {
console.log('[PostLink] :: ', $scope);
console.log('[$Parent] :: ', $scope.$parent.name);
};
return {
restrict: 'E',
scope: {
myPokie: '=pokie',
name: '=name'
},
template: [
'<div>',
'<h1>Directive does not get parent scope</h1>',
'<h1>{{ myPokie }}</h1>',
'<h2>{{ name }}</h2>',
'</div>'
].join(''),
compile: function() {
return {
pre: preLink,
post: postLink
}
}
}
});
http://plnkr.co/edit/FpQtt9?p=preview
Can anyone tell me what is wrong with my code? Why does the directive's Isolate scope return undefined values for 'myPokie' and 'name'?
I've seen other people saying you have to use $scope.watch for this.. but angular's directive docs don't say anything about that.. And I really don't want to use $scope.watch for something so trivial which should work out of the box.
As you declared isolated scope in your directive, that means you are going to provide those value to your directive using attribute.
scope: {
myPokie: '=pokie',
name: '=name'
}
This means your directive scope will not be prototypically inherited from the parent scope. pokie attribute provide the value for myPokie & name attribute will provide value of name for your directive, = indicating a two way binding if your myPokie value change in directive, the same referencing value will change in the parent controller. The same is true for the name attribute.
Your directive element markup should be:
<parse-object pokie="pokie" name="name"></parse-object>
Working Plunkr
You're using isolated scope but you're not passing the variables. The problem is in your HTML:
Change your HTML from:
<parse-object></parse-object>
To:
<parse-object pokie="pokie" name="name"></parse-object>
Isolated scope takes its parameters from the DOM element. So if you have inside the scope declaration:
myPokie: '=pokie',
That means that myPokie variable should be taken from the pokie attribute that's on the scope. Your name: "=name" can be changed to name: "=" since it is exactly the same name.
Plunker

AngularJS: Indicate the scope of a directive

I am building a directive that can be use in different controllers, and I would like to be able of bind the directive to a particular property of my $scope.
I would like to do something like this:
<div ng-app="myapp">
<div ng-controller="myController">
<my-directive-wrapper ng-model="mymodel">
<my-directive-inner ng-repeat="item in items" />
</my-directive-wrapper>
</div>
</div>
With this model:
$scope.mymodel = {
name : "Transclude test",
items : [
{ title : "test1" },
{ title : "test2" },
{ title : "test3" }
]
};
So the directive myDirectiveWrapper gets $scope.mymodel as scope, and nothing else. Then I may put the directive twice, pointing to a different property each.
I have a demo project with the problem here: http://jsfiddle.net/vtortola/P8JMm/3/
And the same demo working normally (without limiting the scope) here: http://jsfiddle.net/vtortola/P8JMm
The question is, how to indicate in the use of my directive that I want to use a particular property of my $scope as scope of my directive. It should be possible to bind the directive to arbitrary properties in the same $scope.
Cheers.
So the basic answer to this question is - you can do what you want to do, but it is a bit more complicated than you might think. To understand what is happening here you need to know about scopes in angular. A scope is essentially an object that contains the data accessible to the view. There are (at least) three ways scopes operate in angular:
Isolated - In this case angular basically creates a brand new scope for the directive. None of the properties are copied over.
Extended - In this case you would start with the root scope but make a shallow copy of it. Objects that are changed will be changed in the root scope but primitives will not be.
Shared - In this case you share share some or even all of the data with the root scope.
Based on your question above, what you what to do here is to extend the parent scope - copying an object to a property with a specific name in the newly created child scope. The way to get this behavior is to manually create a new child scope before the transclude. The two key lines of code to do this are:
// create a "new" scope
var childScope = scope.$new();
// extend using the model binding provided
angular.extend(childScope, scope[iAttr.myModel]);
In the context of your directive this looks like:
.directive('myDirectiveWrapper', ['$compile', function ($compile) {
return {
transclude: true,
restrict: 'E',
compile: function (element, attrs, transclude) {
var contents = element.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
var childScope = scope.$new();
angular.extend(childScope, scope[iAttr.myModel]);
if (!compiledContents) {
compiledContents = $compile(contents, transclude);
}
compiledContents(childScope, function(clone, childScope) {
iElement.append(clone);
});
};
},
template: "<div><h3>{{ name }}</h6><a class='back'>Back</a><div ng-transclude class='list'></div><a class='next'>Next</a>"
}
}])
Now you can specify any variable that you want as the "model" for the child scope and you can then access that directly in the contents of your transcluded code!
SEE THE FIDDLE: http://jsfiddle.net/P8JMm/7/
EDIT: Just for fun, I created a more complicated use case for this directive: http://jsfiddle.net/P8JMm/9/
Note - angular site also has some really good resources to understand scope better. See here.
If you want two way binding to work it's going to be a lot easier to just create a variable on your directive scope rather than apply mymodel directly onto the directive scope.
HTML
<div ng-app="myapp">
<div ng-controller="myController">
<my-directive-wrapper model="mymodel">
<my-directive-inner ng-repeat="item in mymodel.items" />
</my-directive-wrapper>
</div>
</div>
Directive
.directive("myDirectiveWrapper", function(){
return {
scope: {
model: '='
},
restrict: 'E',
transclude: true,
link: function(scope, element, attrs, controller) {
},
template: "<div><h3>{{ model.name }}</h6><a class='back'>Back</a><div ng-transclude class='list'></div><a class='next'>Next</a>"
}
})
http://jsfiddle.net/kQ4TV/
If you don't care about two way binding I suppose you could do something like this but I wouldn't recommend it:
.directive("myDirectiveWrapper", function(){
return {
scope: {
model: '='
},
restrict: 'E',
transclude: true,
link: function(scope, element, attrs, controller) {
angular.extend(scope, scope.model);
},
template: "<div><h3>{{ name }}</h6><a class='back'>Back</a><div ng-transclude class='list'></div><a class='next'>Next</a>"
}
})
http://jsfiddle.net/vWftR/
Here is an example of when that second approach can cause problems. Notice that when you enter something into the input field it will change the directive's name but not the name in the outer scope: http://jsfiddle.net/r5JeJ/

How do I pass an object to a directive?

I'm having issues getting an object to pass to my directive. I believe I've done things correctly, but after failed attempt after failed attempt I must seek help. What did I miss here that's stopping me from passing an array to my directive?
HTML:
<div class="body">
{{orderList.length}} //shows up as 18
</div>
<queue-summary orders="orderList"></queue-summary>
Javascript:
directive('queueSummary', function () {
return {
scope: {
orders: '='
},
replace: true,
restrict: 'E',
templateUrl: '/partials/admin/bits/queue-summary.htm',
link: function (scope, element, attrs) {
console.log(scope, element, attrs); //$attrs.orders show it as the String "orderList" instead of the array
}
}
}).
It's worth noting that you can access the bound value of an attribute you don't have isolate scope on with $eval:
scope.$eval(attrs.orders)
attrs will just show you the string value of an attribute. In order to access the passed object, use the isolate binding you created:
console.log(scope.orders);

Categories