directive's template not accessing the scope when isolating it - angularjs

I'm trying to make a simple directive that gets an attribute and displays it from inside the directive
Here is the directive coed:
angular.module('JJJ')
.directive('jobCard', function () {
return {
template: '<div>name: {{job}}</div>',
restrict: 'E',
scope:{
job: "=j"
},
link: function postLink(scope, element, attrs) {
}
};
});
html usage:
<job-card ng-repeat="j in jobs" job="j.name"></job-card>
The directive doesnt show anything. Why is that?

By defining the scope variable job to =j, you are telling Angular to look for an attribute named j.
Isolated scope works by defining the name of the property in the isolated scope (ex job) and then setting that to a binding setting (ex. = is two-way). In order to name the attribute you can append the name of the attribute to the end of the binding setting (ex. =myAttribute would look for an attribute named myAttribute and set the value on the directive's scope property named job).
angular.module('JJJ')
.directive('jobCard', function () {
return {
template: '<div>name: {{job}}</div>',
restrict: 'E',
scope:{
job: "="
},
link: function postLink(scope, element, attrs) {
}
};
});

When you write an isolated scope like this:
scope: {
job: "=j"
}
It means that outer scope variable referred with attribute j is set with a two-way-data-binding to inner scope variable job.
You should write it like so:
angular.module('JJJ')
.directive('jobCard', function () {
return {
template: '<div>name: {{job}}</div>',
scope:{
job: "="
},
link: function postLink(scope, element, attrs) {
}
};
});

When you want to just display a value from the outer scope (say, this directive is within controller and job is used in controller) use '#' in the directive's scope object instead of '='. As llan pointed out '=' is a two way binding which updates value in the outer scope if the value is changed inside the directive scope. In your example, for instance, changing the value of job in the link function will change the job in the outer scope (in controller) as well.

Related

How to get notified when a custom directive has been initialized in the controller

Let us say I have a custom directive like this applied on a div:
<div my-custom-component></div>
In my controller, I want to define a function like this:
$scope.onAfterMyCustomComponentInit(){
// Do something only once after the component is inited.
}
How do I get the above method in my controller called?
You could pass you controller function to the directive from attribute & as soon as directive link function gets called you can fire the function which you have passed from the attribute. Before passing it from attribute you need to use & with scope variable name inside isolated scope of directive scope: { callback: '&' }
Markup
<div my-custom-component callback="myFunction()"></div>
Directive
app.directive('myCustomComponent', function(){
return {
scope: {
callback: '&'
},
restrict: 'A',
link: function(scope, element, attrs){
//directive has initalized
scope.callback(); //call controller method.
}
}
})

Pass variable to AngularJS directive without isolated scope

I am learning AngularJS directive, and one thing I want to do is to pass some variable $scope.message in the parent scope (a scope of a controller), and I want it to be renamed to param inside the directive alert. I can do this with an isolated scope:
<div alert param="message"></div>
and define
.directive("alert", function(){
return{
restrict: "A",
scope: {
param: "="
},
link: function(scope){
console.log(scope.param) # log the message correctly
}
}
})
But can I do this without using isolated scope? Suppose I want to add another directive toast to the <div toast alert></div> and utilize the same param (keeping the 2-way data-binding), naively I will do
.directive("toast", function(){
return{
restrict: "A",
scope: {
param: "="
},
link: function(scope){
console.log(scope.param)
}
}
})
I surely will get an error Multiple directives [alert, toast] asking for new/isolated scope on:<div...
So in all, my question is, how to rename parent scope variable without isolated scope, and how to share variables when two directives are placed on a single DOM?
Modify your toast directive:
.directive("toast", function(){
return{
restrict: "A",
link: function(scope, elem, attrs){
var param = scope.$eval(attrs.param);
console.log(param)
}
}
})
Example fiddle.
Since toast is now on the same scope as the parent would have been (if it was allowed to be isolate scope), you can simply call $eval on scope with the param attribute to get the value.

Getting the children of an element with an attribute directive

I have a directive that is restricted to attribute. I want to do either of 2 things. If a certain condition is met, I want to use the children for the element with the directive attribute as the model (thats the content) or if that condition is not met then I want to data bind from a service instead, so the directive would replace the children with something i was given. I have a directive that is doing the latter but Im finding it very hard to have it grab the children before the compiler comes in and replaces it with its template... Anyone know how this is done if its possible?
I think what you're looking for is element.context in your directive's link (or compile) function.
Inside your link function (pre or post), the original element that your directive was found on is stored in the passed-in element's context property. So if your service call returns no data, you can just replace the compiled element with the original element by doing element.replaceWith(element.context).
So your directive would look something like this:
.directive('ttContent', ['mySvc', function (mySvc) {
return {
restrict: 'A',
replace: true,
transclude: false,
template: '<div class="content new-content" ng-bind-html="htmlContent | sanitize"></div>',
scope: {
testDataReturned: '#'
},
link: {
pre: function (scope, element, attrs) {
},
post: function (scope, element, attrs){
mySvc.fetchContent().then(function success(data){
if (data) {
scope.htmlContent = data;
} else {
// element.context is our original, pre-compiled element
element.replaceWith(element.context);
}
}, function fail(data){
element.replaceWith(element.context);
});
}
}
};
}]);
Here's a plunk.

Why is what I set in rootScope not available in a directive?

I am setting the following:
function appRun(
$rootScope
) {
$rootScope.abc = 99;
}
I am calling a directive like this:
<admin-retrieve-button ctrl="exam" home="home" Network="Network"></admin-retrieve-button>
Here's my directive:
app.directive('adminRetrieveButton', ['stateService', function (stateService) {
return {
scope: {
ctrl: '=',
home: '=',
Network: '=',
abc: '='
},
restrict: 'E',
template: "xxxx {{ abc }} dddd",
link: function (scope, element, attrs) {
scope.stateService = stateService;
scope.entity = attrs["entity"];
}
};
}]);
However when my HTML page comes up it shows:
xxxx dddd
Can someone tell me why the rootScope value of 99 does not show up?
Because you're declaring an isolate scope on your directive, thus creating a new parent less scope. One way to fix this would be to obviously explicitly pass in abc in your HTML:
<admin-retrieve-button ctrl="exam" abc="abc" home="home" Network="Network"></admin-retrieve-button>
Or you could change your directive to not create a scope of its own:
scope:false
or just leave the scope property alone (don't declare it), however passing in a directives dependencies through a scope of its own is good practice IMO, it's more explicit and gets rid of hidden dependencies.

Is it ok watch the scope inside a custom directive?

I'm trying to write a directive to create a map component so I can write something like:
<map></map>
Now the directive looks like this:
angular.module('myApp')
.directive('map', function (GoogleMaps) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
scope.$watch('selectedCenter', function() {
renderMap(scope.selectedCenter.location.latitude, scope.selectedCenter.location.longitude, attrs.zoom?parseInt(attrs.zoom):17);
});
function renderMap(latitude, longitude, zoom){
GoogleMaps.setCenter(latitude, longitude);
GoogleMaps.setZoom(zoom);
GoogleMaps.render(element[0]);
}
}
};
});
The problem is that 'watch' inside the directive doesn't looks very well thinking in the reusability of the component. So I guess the best thing is being able to do something like:
<map ng-model="selectedCenter.location"></map>
But I don't know if it's even a good thing using angular directives inside custom directives or how can I get the object indicated in the ng-model attribute in the custom-directive's link function.
You will need to do something like that
angular.module('myApp')
.directive('map', function (GoogleMaps) {
return {
restrict: 'E',
scope: {
ngModel: '=' // set up bi-directional binding between a local scope property and the parent scope property
},
as of now you could safely watch your scope.ngModel and when ever the relevant value will be changed outside the directive you will be notified.
Please note that adding the scope property to our directive will create a new isolated scope.
You can refer to the angular doc around directive here and especially the section "Directive Definition Object" for more details around the scope property.
Finally you could also use this tutorial where you will find all the material to achieve a directive with two way communication form your app to the directive and opposite.
Without scope declaration in directive:
html
<map ng-model="selectedCenter"></map>
directive
app.directive('map', function() {
return {
restrict: 'E',
require: 'ngModel',
link: function(scope, el, attrs) {
scope.$watch(attrs.ngModel, function(newValue) {
console.log("Changed to " + newValue);
});
}
};
});
One easy way you can achieve this would be to do something like
<map model="selectedCenter"></map>
and inside your directive change the watch to
scope.$watch(attrs.model, function() {
and you are good to go

Resources