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

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

Related

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).

Unable to update Child controllers scope valriable

Im new to angular js and im not able to figure out how to change the child controller scope variable from parent controller. Here is the code snippet for that:
var mainApp = angular.module("mainApp", []);
var parentCtrl = function($rootScope, $scope, shareService, $log){
shareService.setDetails($scope.pdetails);
}
var mainCtrl1 = function($rootScope, $scope, shareService, $log){
$scope.msg = "Controller 1";
$scope.details = shareService.details;//shareService.details;
}
var mainCtrl2 = function($rootScope, $scope, shareService){
$scope.msg = "Controller 2";
$scope.details = shareService.details;//shareService.details;
}
parentCtrl.$inject = ["$rootScope", "$scope", "shareService", "$log"];
mainCtrl1.$inject = ["$rootScope", "$scope", "shareService", "$log"];
mainCtrl2.$inject = ["$rootScope", "$scope", "shareService", "$log"];
mainApp.controller("parentController", parentCtrl)
.controller("mainController1", mainCtrl1)
.controller("mainController2", mainCtrl2)
.factory("shareService", function(){
var shareData = {
details : "sadfgs detaisdfadsfasdf..",
setDetails: function(value){
this.details = value;
}
};
return shareData;
});
<html>
<head>
<title>Angular JS Views</title>
<script src='lib/angular.js'></script>
<script src='js/mainApp.js'></script>
<script src='js/studentController.js'></script>
</head>
<body ng-app = 'mainApp' ng-controller='parentController' ng-strict-di>
<div ng-controller='mainController1'>
1. Msg : {{msg}}<br/>
Share Details: {{details}}<br/><br/>
</div>
<div ng-controller='mainController2'>
2. Msg : {{msg}}<br/>
Share Details: {{details}}<br/><br/>
</div>
<input type='text' ng-model='pdetails'/>
</body>
</html>
Here is the Plunker link:
https://plnkr.co/edit/hJypukqMmdHSEZMVnkDO?p=preview
In order to change value of child controller from parent controller you can use $broadcast on $scope.
syntax
$scope.$broadcast(event,data);
$broadcast is used to trigger an event(with data) to the child scope from current scope.
In child controller use $on to receive the event(with data).
Here id the code snippet:
app.controller("parentCtrl",function($scope){
$scope.OnClick=function()
{
$scope.$broadcast("senddownward",$scope.messege);
}
});
app.controller("childCtrl",function($scope){
$scope.$on("senddownward",function(event,data)
{
$scope.messege=data;
});
});
In this example I am broadcasting the event on ng-click,you can use some other custom event.like $watch on $scope.
See this example
https://plnkr.co/edit/efZ9wYS2pukE0v4JsNCC?p=preview
P.S. you can change the name of event from senddownward to whatever you want
You can access the parent's scope properties directly due to the scope inheritance:
<div ng-controller='mainController1'>
Share Details: {{pdetails}}
</div>
Your example does not work because the controllers get executed only once before the view is rendered, pdetails is empty at that moment.
To monitor the changes to pdetails, you can use $watch in the child controller:
$scope.$watch('pdetails', function(newVal) {
$scope.details = newVal;
});

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

Share data between directive scope and controller $scope?

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!

AngularJS directive using another controller

I'm trying to create a directive that will output a HTML-template that is using data from a controller.
In sample.js I've added a module, controller and directive
var app = angular.module("myApp", []);
app.controller("MyCtrl", function($scope) {
$scope.someProperty = true;
})
app.directive("myElement", function() {
return {
restrict: "E",
scope: {name: "#"},
template:
'<div ng-show="someProperty">' +
' <p>This element is called {{name}}</p>' +
'</div>',
replace : true,
transclude : false
}
})
I'm using the element with the following HTML
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript" src="sample.js"></script>
</head>
<body ng-controller="MyCtrl">
<div><my-element name="Mark"></my-element></div>
<div><my-element name="Vink"></my-element></div>
</body>
</html>
Since the controller is created in the body, I would expect the child-element to be able to use it's properties/methods. But there's no data showing up (without the ng-show, the data shows fine).
In this simplified sample I could move the ng-show to the DOM-element in the HTML, but in my actual application this wouldn't be an option. I really need my directive to be able to use properties (and methods) from my controller.
Is this possible? And what did I miss to get it to work?
Since you are using an isolated scope you have to declare someProperty to use it like this
app.directive("myElement", function() {
return {
restrict: "E",
scope: {
name: "#",
someProperty: "="
},
template:
'<div ng-show="someProperty">' +
' <p>This element is called {{name}}</p>' +
'</div>',
replace : true,
transclude : false
}
});
and you can use it like this
<my-element name="Vink" some-property="someProperty"></my-element>

Categories

Resources