Share data between directive scope and controller $scope? - javascript

Here I created some sample for date picker, which is working fine but I need to set min and max date dynamically.. so i am passing the start and end date from
Html like this my-datepicker min="2013-07-23" max="2015-07-23" in directive scope i get the value and I need to set this value in controller $scope.datepickerOptions = { startDate :min, endDate:max} some thing like this..
var app = angular.module('myapp', ['ng-bootstrap-datepicker'])
app.directive('myDatepicker', function() {
return {
restrict: 'E',
template: '<input type="text" ng-datepicker ng-options="datepickerOptions" ng-model="ngModel">',
scope: {
date: '=',
ngModel: '=',
min: '=',
max: '=',
},
controller: function($scope) {
$scope.datepickerOptions = {
format: 'yyyy-mm-dd',
autoclose: true,
weekStart: 0,
startDate :'2013-07-23',
endDate:'2015-07-23'
};
}
};
})
app.controller('AppCtrl', ['$scope', function ($scope) {
$scope.date = '2013-08-12'
}]);
var appboot = angular.bootstrap(document, ['myapp']);
<link href="https://rawgit.com/cletourneau/angular-bootstrap-datepicker/master/dist/angular-bootstrap-datepicker.css" rel="stylesheet"/>
<link href="http://netdna.bootstrapcdn.com/bootstrap/2.0.4/css/bootstrap.min.css" rel="stylesheet"/>
<script src="http://code.jquery.com/jquery-2.0.2.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/2.0.4/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script src="https://rawgithub.com/cletourneau/angular-bootstrap-datepicker/master/dist/angular-bootstrap-datepicker.js" charset="utf-8"></script>
<body>
<div>
<div ng-app="myapp" ng-controller="AppCtrl">
<my-datepicker ng-model ="date" min="2013-07-23" max="2015-07-23"></my-datepicker>
<input id="datepickerMirror" type="text" data-ng-model="date">
</div>
</div>
</body>

$scope in the directive controller IS the isolated scope from the directive. You can just grab the values from $scope.min and $scope.max.
Update The reason your code can't do this is because you're using the '=' binding which causes your directive to look for a variable named 2013-07-23 on your scope. You either need to put your value in a variable, or change the binding to '#' and use interpolation (the curly braces {{value}}), or surround your date value with single quotes inside the double quotes as in min="'2013-07-23'" max="'2015-07-23'".
https://plnkr.co/edit/Gp5SBtIAuLq5BzzIdKfp?p=preview
var app = angular.module('myapp', ['ng-bootstrap-datepicker'])
app.directive('myDatepicker', function() {
return {
restrict: 'E',
template: '<input type="text" ng-datepicker ng-options="datepickerOptions" ng-model="ngModel">',
scope: {
dateval: '=',
ngModel: '=',
min: '=',
max: '=',
},
controller: function($scope) {
$scope.datepickerOptions = {
format: 'yyyy-mm-dd',
autoclose: true,
weekStart: 0,
startDate : $scope.min,
endDate: $scope.max
};
}
};
})
app.controller('AppCtrl', ['$scope', function ($scope) {
$scope.dateval = '2013-08-12';
$scope.min = '2013-07-23';
$scope.max = '2015-07-23';
}]);
var appboot = angular.bootstrap(document, ['myapp']);
html
<!DOCTYPE html>
<html>
<head>
<link href="//rawgit.com/cletourneau/angular-bootstrap-datepicker/master/dist/angular-bootstrap-datepicker.css" rel="stylesheet"/>
<link href="//netdna.bootstrapcdn.com/bootstrap/2.0.4/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//code.jquery.com/jquery-2.0.2.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/2.0.4/js/bootstrap.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script src="//rawgithub.com/cletourneau/angular-bootstrap-datepicker/master/dist/angular-bootstrap-datepicker.js" charset="utf-8"></script>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<div>
<div ng-app="myapp" ng-controller="AppCtrl">
<my-datepicker ng-model="dateval" min="min" max="max"></my-datepicker>
<input id="datepickerMirror" type="text" data-ng-model="dateval">
</div>
</div>
</body>
</html>

As mentioned in the previous answer's Updated comments, the reason it's not working on UPDATE is that you're "binding" (scope params with "=") to a string literal. You're likely also getting a console error when you attempt to set that variable, something along the lines of object "non-assign".
That being said, is there a reason your directive NEEDS an isolated scope? If you just set the directive up using "scope: true", then your directive will prototypically inherit from the parent scope. This reduces portability, but it doesn't look like you're really shooting for that at this point.
To setup useful prototypical inheritance you'll need to also use the "as" syntax of ng-controller in your HTML view file. For the sake of example, let's say:
ng-controller="AppCtrl as appCtl"
Then move the initialization of datepickerOptions from your directive into your main AppCtrl controller. Personally I prefer the "dot" syntax as opposed to littering the scope with variables that are hard to track, so assign it to your controller instead of scope:
this.datepickerOptions = { /* min, max, etc */ }
Now in your directive (using scope:true), you can access that controller via the directive $scope. So in your directive's controller function:
$scope.datepickerOptions = $scope.appCtl.datepickerOptions
Note that I chose the "dot" syntax here because otherwise the prototypical inheritance would create a new scope element for datepickerOptions in your directive instead of traversing up and checking the scope chain. By using the dot syntax, the previous scope variable (appCtl) is accessed, and then the sub-object lookup (datepickerOptions) causes the app to traverse the scope chain up and get the object instead of shadowing it.
The last piece to address is ngModel. If you're following along up to now, all you have to do is set the ng-model (in your directive template) to read "appCtl.modelName" where modelName is the variable you want to use. Now your controller will have that variable assigned directly to it. If you want to do something when that value changes, add this to your AppCtrl controller:
// create a variable so that it can be used in callbacks where "this" changes
var _this = this;
// Create this by hand since its ng-model binding is added dynamically by the directive template
_this.modelName = null;
$scope.$watch(function() { return _this.modelName; }, function(val, oldVal)
{
// do something here, remembering that _this contains a reference to the controller itself
});
Also note that in doing this you can get rid of that other input (datepickermirror) since all your data is already in your controller, and simply accessed by the directive.
Hope that helps!

Related

How to pass $scope in Angular directive

I am new to AngularJS. I want to return template to directive with addition and subtraction of two numbers. I am passing $scope in function but it is not working.
I'm learning from Angular Modules with Directive
here is the code :
<html>
<head>
<title>Angular JS </title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body>
<div ng-app="Compl">
<input type=number ng-model="no1" placeholder="Enter the First Number" />
<input type=number ng-model="no2" placeholder="Enter the First Number" />
<NoSum></NoSum>
<NoSub></NoSub>
</div>
<script>
var app = angular.module("Compl", []);
app.directive("NoSum", function($scope) {
return {
template: "Sum of Two Number" + ($scope.no1 + $scope.no2)
};
});
app.directive("NoSub", function($scope) {
return {
template: "Sub of Two Number" + ($scope.no1 - $scope.no2)
};
});
</script>
</body>
</html>
If you're not using isolated scope, you should be able to just use no1 and no2 directly in the template. assuming those are variables in the parent scope.
<script>
var app = angular.module("Compl", []);
app.directive("noSum",function(){
return{
template : "Sum of Two Number {{(no1 + no2)}}"
};
});
app.directive("noSub",function(){
return{
template : "Sub of Two Number {{(no1 - no2)}}"
};
});
</script>
You should also rename your directives, since capitalized letters have special meaning in angular. So with my changed names above, your html should look like this:
<no-sum></no-sum>
<no-sub></no-sub>
Here is a Plunker showing it working as expected
This works because without isolated scope, your directives inherit the scope of their parent, which in your case is the $rootScope. If you used isolated scope instead, you would have to pass in the variables through html attributes.
What you can do is
a) use the 'parent scope' .. so te scope of the controller of the view which contains your directive and so like this:
app.directive("NoSum",function($scope){
return {
template: "Sum of Two Number" + (scope.no1 + scope.no2) //<-- don't use scope with $ ... but only scope
link: function(scope, element, attrs) {
element.bind('error', function() {
if (attrs.src !== attrs.errSrc) {
attrs.$set('src', attrs.errSrc);
}
});
}
};
});
2 - use 'isolated scope' .. so you force to pass the scope trought your item like so:
app.directive("noSum",function($scope){
return {
restrict:'EAC'
template: "Sum of Two Number" + (item.no1 + item.no2) //<-- here you use item and in html you use like: <no-sum item="myvarscoped"></no-sum>
scope:{ item : "="}
link: function(scope, element, attrs) {
element.bind('error', function() {
if (attrs.src !== attrs.errSrc) {
attrs.$set('src', attrs.errSrc);
}
});
}
};
});
As Vivz has already mentioned, you can't pass scopes. Best practice is to share data between controllers/directives is to use factories. See this link for particular example.

How to access controller scope from dynamically created directive

Basically I am trying to access controller scope property from directive's controller function. I am doing it through $parent property. It works fine for static directive but not for dynamically created directive.
please have a look on my plunker
Dynamic Directive
In a plunker, when I click on folder with Id = 1. all goes good and folder path shows as "1 path". Same goes for folder with Id = 2.
But it does not work for dynamically appended folder with Id = n
I am somewhat new to angular. Any help would be much appreciated.
Updated Answer
In light of the latest requirement:
I am trying to call the directive function (i.e updateMap) from
controller.
You can use a Service to share variables between Controllers and Isolated Directives. In the example below, the Service holds the function that will be executed. Each directive when clicked will set the Service's function to it's own updateMap() function. Then the Controller in onFolderPathClicked() calls the Services executeFunction() function, which runs the previously set function.
script.js:
var module = angular.module('testApp', []);
module.service('updateMapService', function(){
var updateMapFunction = null;
this.executeFunction = function(){
updateMapFunction();
};
this.setFunction = function(fn){
updateMapFunction = fn;
};
});
module.controller('crtl', function($scope, updateMapService) {
$scope.onFolderPathClicked = function(){
updateMapService.executeFunction();
};
});
module.directive('folder', function($compile) {
return {
restrict: 'E',
scope: {
id: '#',
folderPath: "="
},
template: '<p ng-click="onFolderClicked()">{{id}}</p>',
controller: function($scope, $element, updateMapService) {
$scope.onFolderClicked = function(){
updateMapService.setFunction(updateMap);
addFolder();
};
var addFolder = function() {
$scope.folderPath = $scope.id + ":click here for calling update map";
var el = $compile('<folder id="n" folder-path="folderPath"></folder>')($scope);
$element.parent().append(el);
};
var updateMap = function() {
alert('inside updateMap()..' + $scope.id);
}
}
}
});
index.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-app="testApp" ng-controller="crtl">
<div>FolderPath : <a ng-click="onFolderPathClicked()">{{ folderPath }}</a> </div>
<folder id="1" folder-path="folderPath"></folder>
<folder id="2" folder-path="folderPath"></folder>
</div>
</html>
You could also move folder-path into a Service to save from passing it in as an attribute. The code smell being that passing it in as an attribute means doing so twice, whereas in a Service it means setting it and getting it once (code reuse).

How to access directive's controller $scope properties inside controller

I have to access value from directive's controller inside my controller.
<div ng-controller="myCtrl">
<my-directive atr="xyz"></my-directive>
</div>
//here is my directive
app.directive('myDirective',function() {
return {
restrict : 'E',
replace : false,
scope :{
atr :'#'
},
controller : function($scope) {
console.log($scope.atr); //xyz
$scope.keyPoint ="this is what i want to access inside myCtrl";
}
}
});
//here is ctrl
app.controller('myCtrl',function($scope){
//how can I access keyPoint here
})
Isolating scope:
You Should use two-way binding for the keypoint to achieve this.
scope :{
atr :'#',
keyPoint: '='
},
What this will do is when ever you change the value in Directive, it reflects in your Controller, and vice-versa
// Instantiate the app, the 'myApp' parameter must
// match what is in ng-app
var myApp = angular.module('myApp', []);
// Create the controller, the 'ToddlerCtrl' parameter
// must match an ng-controller directive
myApp.directive('myDirective',function() {
return {
restrict : 'E',
replace : false,
scope :{
atr :'#',
keyPoint: '='
},
controller : function($scope) {
console.log($scope.atr); //xyz
$scope.keyPoint ="this is what i want to access inside myCtrl";
}
}
});
myApp.controller('myCtrl',function($scope,$timeout){
$timeout(function(){
alert($scope.keypoint)
},500)
})
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script data-require="angular.js#1.2.7" data-semver="1.2.7" src="http://code.angularjs.org/1.2.7/angular.js"></script>
<link href="style.css" rel="stylesheet" />
<script src="script.js"></script>
</head>
<body>
<h1>Starter AngularJS app</h1>
<div ng-controller="myCtrl">
<my-directive atr="xyz" key-point="keypoint"></my-directive>
</div>
{{keypoint}}
</body>
</html>
Please run this snippet
Here is the fiddle
Without Isolating scope:
If you want to get the scope of the controller in the directive, dont Isolate the scope in the directive.
If you Isolate the scope, you cannot get controller's scope.
// Instantiate the app, the 'myApp' parameter must
// match what is in ng-app
var myApp = angular.module('myApp', []);
// Create the controller, the 'ToddlerCtrl' parameter
// must match an ng-controller directive
myApp.directive('myDirective',function() {
return {
restrict : 'E',
replace : false,
controller : function($scope) {
console.log($scope.atr); //xyz
$scope.keyPoint ="this is what i want to access inside myCtrl";
}
}
});
myApp.controller('myCtrl',function($scope,$timeout){
$scope.atr="xyz"
$timeout(function(){
alert($scope.keyPoint)
$scope.$apply();
},500)
})
<script data-require="angular.js#1.2.7" data-semver="1.2.7" src="http://code.angularjs.org/1.2.7/angular.js"></script>
<link href="style.css" rel="stylesheet" />
<script src="script.js"></script>
<body ng-app="myApp">
<h1>Starter AngularJS app</h1>
<div ng-controller="myCtrl">
<my-directive ></my-directive>
<h1>{{keyPoint}}</h1>
</div>
</body>
Fiddle for second snippet

transclude usage in simple Directive Example

In the following simple example I am printing the name model from controller by directive on the view. The example is running fine, but what is the use of transclude I cannot understand. Can someone explain its usage?
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js" ></script>
</head>
<body ng-app="myApp" ng-controller="myCtrl">
<people></people>
<script>
//module declaration
var app = angular.module("myApp",[]);
//controller declaration
app.controller('myCtrl',function($scope){
$scope.name = "Peter";
});
//directives declaration
app.directive('people',function(){
return{
restric: 'E',
template: '<div>{{name}}</div>',
transclude: true
}
});
</script>
</body>
</html>
Your code doesn't really demonstrate what transclude does:
Look at this plunk and change the true/false value:
Plunk
You will notice the effect now hopefully. The source from plunkr, with a couple of modifications.
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.5.3" data-semver="1.5.3" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="myApp" ng-controller="myCtrl">
<people>HI there</people>
<script>
//module declaration
var app = angular.module("myApp",[]);
//controller declaration
app.controller('myCtrl',function($scope){
$scope.name = "Peter";
});
//directives declaration
app.directive('people',function(){
return{
restric: 'E',
template: '<div><ng-transclude></ng-transclude>: {{name}}</div>',
transclude: false
}
});
</script>
</body>
</html>
So when it is true, you will see that the contents are transcluded,
So it says HI There: Peter
When False, it removes the HI There, but keeps the name and my colon:
: Peter
Essentially, these are wrappers around any arbitrary content.
Supposing I have an accordion directive that shows or hides any content that you use it with with an animation.
app.directive('akordion', [function() {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<div class="accordion-wrapper">'
+'<div class="transcluded" ng-transclude></div>'
+'</div>',
link: function(scope, elem, attrs) {
scope.$watch(attrs.show, function(newVal){
toggle(newVal);
});
function toggle(show) {
var h = (show) ? 0 : '600px';
$(elem).css({ maxHeight: h });
}
}
}
}]);
You'd use it like this:
<div akordion="" id="league-admin">
<div>
foo
</div>
<my-directive></my-directive>
</div>
And the result (generated HTML) is:
<div class="accordion-wrapper" id="league-admin">
<div class="transcluded">
<div>
foo
</div>
<div id="my-directive">...</div>
</div>
</div>
The point is that by calling the akordion="", you take whatever is inside it and put it in the template (<div class="transcluded" ng-transclude>). In other words, the akordion directive wraps over (transcludes) the content you use it on.
Another example would be modal windows. You don't want to repeat the code that defines the modal each time you want to use it, so you define it once, and use transclusion to put any content into it. Check out modal in Bootstrap UI.
Basically If you have some content inside your directive it will be automatically replaced by the directive content
For Example, if you have<people>Test transclude</people> The Test transclude string will be automatically replace by angular when it process the directive. But what if you want 'Test transclude ' also to be displayed ? Here is where transclude come in to action.
Consider the following
app.directive('people',function(){
return{
restric: 'E',
template: '<div><div ng-transclude></div>{{name}}</div>',
transclude: true
}
});
Now the string 'Test transclude' will be also displayed inside tag
And this is the plunker link Plunk

view does not update with either scope or controller properties

I have some trouble identifying a view update that is not happening neither when I set a property on the $scope nor when I set a property on the controller prototype.
I have create a quick example of what I am doing to illustrate the issue.
I have, for instance the following view:
<html data-ng-app="test">
<head>
<script data-require="angular.js#1.2.18" data-semver="1.2.18" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<h1>Hello Plunker!</h1>
<div data-catalog="{ 'type': 'Category1', 'key': '252' }">
<span class="text-danger">{{query | json}}</span>
<span class="text-danger">{{catalog.model | json}}</span>
</div>
</body>
</html>
And a simple module with a directive and a controller:
angular
.module("test", [])
.controller("CatalogCtrl", ["$scope", "$parse", "$location", "$q", function ($scope, $parse, $location, $q) {
var catalog = this,
listener = function () {
var query = {},
params = $location.search();
try
{
query = $parse($scope.query)();
}
catch (exception)
{
console.error(exception);
}
if (_.isEmpty(query))
{
return;
}
$q
.all([
scroll({ top: 0 }),
Catalog.searchProducts(_.assign(query, params))
])
.then(function (response) {
catalog.model = response[1];
console.log(catalog.model, response[1]);
})
.catch(function () {
catalog.model = {};
})
.finally(function () {});
};
catalog.model = {test:""};
//$scope.listen("$locationChangeSuccess", listener);
}])
.directive("catalog", function () {
return {
controller: "CatalogCtrl as catalog",
scope: {
query: "#catalog"
},
restrict: "A",
replace: false
};
});
The issue is that neither {{query | json}} nor {{catalog.model | json}} are rendered in the view and I am not sure what is the cause of it. It might be something I am missing or doing wrong, but I could use some help with it if anyone spots my mistake :)
There are a few issues with your code...
1.
Your directive has it controlelr attached and you place some values on its scope (query, catalog etc). Then you try to access those values from an element that is outside of the directive (and thus has a different scope that knows nothing about query, catalog etc. E.g.
<!-- Let's say somehow DIV#1 has scope $001 -->
<div id="1">
<!-- Somehow DIV#1.1 creates a new scope ($002) -->
<div id="1.1" some-directive>
<!-- DIV#1.1.1 will have access to scope $002 -->
<!-- (under certain cirsumstances) -->
<div id="1.1.1"></div>
</div>
<!-- DIV#1.2 (which is outside of DIV#1.1) -->
<!-- will have access to scope $001 (but not $002) -->
<div id="1.2"></div>
</div>
2.
To make things even more complicated, your directive creates an isolate scope, which means that any content it has will not see your directive's scope, but its parent scope. I.e. in the example above, DIV#1.1.1 will have access to scope $001, not $002.
What you can do about it (which basically mean, explicitly state that your directive's content should be included (transcluded) into its template. This gives you greater control on what's going on and allows you to bind the content of your directive to the scope you want (i.e. your directive's isolate scope).
The resulting code should look like this:
<div data-pj-catalog="{ 'type': 'Category1', 'key': '252' }">
...
<div class="col-12">
...
<span class="text-danger">{{query | json}}</span>
<span class="text-danger">{{catalog.model | json}}</span>
...
<div>
</div>
.directive('pjCatalog', function () {
return {
restrict: 'A',
transclude: true,
template: '<div ng-transclude></div>',
controller: 'CatalogCtrl',
scope: {
query: '#pjCatalog'
},
link: function (scope, elem, attrs, ctrls, transFn) {
transFn(scope, function (cloned) {
elem.empty().append(cloned);
});
}
};
})
See, also, this short demo.
Note:
This is considered "advanced" directive stuff (so it sounds (and is) much more complicated than most directive stuff) and should be required in rae cases only.
I am pretty sure there is a much easier way to achieve what you want (e.g. using a non-isolate scope) with slight modifications (but I am not sure what you want in order to help further.

Categories

Resources