Pass variable to AngularJS directive without isolated scope - angularjs

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.

Related

How Angular assign scope when we dynamic add DOM element in it

All:
I am pretty new to Angular Directive, from API doc:
https://code.angularjs.org/1.3.20/docs/api/ng/function/angular.element
scope() - retrieves the scope of the current element or its parent.
Requires Debug Data to be enabled.
And say I have a directive like:
.directive("test", function($compile){
return {
restrict: "AE",
scope: { data: "=" },
replace:true,
template:"<div id='viz'></div>",
link: function(scope, EL, attrs){
console.log(angular.element(EL).scope(), EL);
console.log(scope);
}
}
})
And HTML like:
<body ng-controller="main">
<test></test>
</body>
One interesting thing is:
The two scopes printed out in console are not same scope, I wonder why this happens?
If like the API doc says: scope() will return current element's scope, if not, then its parent's. I think they both should return the isolate scope of test, but why angular.element(EL).scope() return its parent's scope?
Thanks

What is the difference between using "require:ngController" and "controller:" in a directive?

I'm switching from a directive created using:
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
controller: "swatchesController"
};
and
<swatches-directive></swatches-directive>
to using:
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
require: "ngController"
};
and
<swatches-directive ng-controller="swatchesController"></swatches-directive>
This seems to have unanticipated side-effects on existing watches belonging to other directives against scope variables that the swatches-directive assigns to. From what I understand, the new way introduces a new scope, so assigning watched variables to the parent scope seems like it should work, but those watches refuse to trigger.
Are there fundamental differences between the two methods used above?
Few points to note:
You should use the controllerAs property to segregate the
controllers. It's considered as a best practice. You can read it here
Ideally, you should provide the controller within the directive itself. The way you're doing it causes spagetti scopes. Keep the directive scope separate from parent scopes. If you want you can pass the required dependencies to a directive.
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
require: "ngDirective", //Get the controller of that directive
link : function(scope, element, attributes, ngController){
//With require, you say: get the controller of that directive. You can
//then use it as 4th parameter (ngController)
}
};
Note: you can pass multiple controllers
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
require: ["ngDirective1", "ngDirective2"], //Get the controller of that directive
link : function(scope, element, attributes, controllers){
var ctrl1 = controllers[0];
var ctrl2 = controllers[1];
//Require can be a string or, as this example, an array.
}
};
The directive passed inside require must be in your directive.
If it is not, you can say: find it on container elements with ^:
require : '^ngDirective'

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.

directive's template not accessing the scope when isolating it

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.

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