myApp.controller('myController', ['$scope', function($scope) {
$scope.a = 0;
$scope.find = function() {
$scope.a += 1;
}
}])
.directive('myLoading', [function() {
if ($scope.a % 2 === 0) {
return {
template: "<div>Hi</div>"
}
} else {
return {
template: ''
}
}
}]);
I'm trying to manipulate the DOM every time I recieve a click event from a button. This is supposed to make a switch between the templates each time I recieve the click. But I'm getting an error in the console log. $scope is not defined, the one in the directive.
$scope inside controller and $scope inside directive aren't the same variable.
There's many ways to communicate between controller and directive. For example directives attribute's value, but you can also use $broadcast / $emit, or $rootScope.
More info.
You could not directly access the $scope variable OR $scope dependency inside directive factory function. You could access the scope of that element inside link function of directive, but its depends on scope property of directive.
The thing which you wanted to achieve is much easier than what you are trying.
Directive
.directive('myLoading', [function() {
return {
template: '<div ng-if="(a % 2 === 0)">Hi</div>'
}
}]);
Related
I am using directives to create a component library in AngularJS 1.5. Hence, my directives need to have isolate scopes.
Some of my directives have callbacks so you can pass in a function to get invoked by the directive. However, when that callback is invoked by the directive, it doesn't seem like the changes to $scope attributes are fully updated like I would expect them to be.
Here is a Plunker that shows this behavior:
http://embed.plnkr.co/Rg15FHtHgCDExxOYNwNa/
Here is what the code looks like:
<script>
var app = angular.module('myApp', []);
app.controller('Controller', ['$scope',function($scope) {
// initialize the value to something obvious
$scope.clickersValue = "BEFORE";
// when this call back is called we would expect the value to be updated by updated by the directive
$scope.clickersCallback = function() {
//$scope.$apply(); // $apply is not allowed here
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
}
]);
app.directive('clicker', [function() {
return {
restrict: 'EA',
template: '<div ng-click="clicked()">click me!</div>',
controller: ['$scope', function($scope) {
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$scope.myUpdate();
}
}],
scope: {
"newValue": "=",
"myUpdate": "&"
}
};
}]);
</script>
So when clickersCallback gets invoked the clickersValue attribute still has the old value. I have tried using $scope.$apply but of course it isn't allowed when another update is happening. I also tried using controller_bind but got the same effect.
Wrap the code inside clickersCallback function in a $timeout function.
$timeout(function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
});
Updated plunker
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
source
Edit 1: As the OP said below, the user of the directive should not have to write any "special" code in his callback function.
To achieve this behavior I changed the $timeout from de controller to the directive.
Controller callback function (without changes):
$scope.clickersCallback = function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
Directive code (inject $timeout in the directive):
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$timeout(function() {
$scope.myUpdate();
});
}
Updated plunker
I'm pretty new with angular and I've read a lot of threads here and googled this topic but I cannot get a clear answer. what i am really trying to achieve is. lets suppose I have a controller A, this is a actual source for data. I passed it to one directive through binding it to a HTML. From this directive I am acually getting the source at another controller.
So I need to find out the way where I can change the data of controller when the data of controller A gets changed.
Controller A
angular.module('page.leadAndOpportunity.ctrl', []).controller('LeadAndOpportunityController', ['$scope', '$rootScope', '$timeout', function ($scope, $rootScope, $timeout, leadAndOpportunityService) {
$scope.selectDataSource = function (condition) {
var dataSource = [];
var dataSource = $scope.leadsDataSource.filter(function (item) {
return item.typeName === condition;
});
$scope.leadsDataSource = [];
$scope.leadsDataSource = dataSource;
console.log($scope.leadsDataSource);
}
}]);
HTML
<ng-senab-grid datasource="{{ leadsDataSource }}" defaultdata="{{defaultColumns}}" skipdata="{{ skipColumns }}" enablepersonalisation="true"></ng-senab-grid>
Directive
angular.module('page.gridView.drct', []).directive("ngSenabGrid", ["$rootScope", function ($rootScope) {
return {
restrict: "E",
templateUrl: "pages/gridView/page.gridView.tpl.html",
scope: {
enablePersonalisation: "#enablepersonalisation",
datasource: "#datasource",
defaultdata: "#defaultdata",
skipdata: "#skipdata"
},
}
}]
);
Controller B
var _datasource = JSON.parse($scope.datasource);
//rest the data follows
So when $scope.leadsDataSource gets changes on Controller A, then the
var _datasource = JSON.parse($scope.datasource);
also should get changed
I dont know if it is possible or not. But I need to change the data
Thanks in advance
remove the curly brackets of the variable.since this is a directive no need to add curly brackets
<ng-senab-grid datasource="leadsDataSource" defaultdata="defaultColumns" skipdata="skipColumns" enablepersonalisation="true"></ng-senab-grid>
if u want to get the value of the variable then use "=" if u use "&" it will only get the string
scope: {
enablePersonalisation: "=enablepersonalisation",
datasource: "=datasource",
defaultdata: "=defaultdata",
skipdata: "=skipdata"
},
also inject the directive module to ur angular module
angular.module('page.leadAndOpportunity.ctrl', ['page.gridView.drct'])
A simple explanation to keep in mind about different types of scopes would be below.
# Attribute string binding (String)
= Two-way model binding (model)
& Callback method binding (method)
According this you should be using Two-way binding instead of Attribute string binding because The model in parent scope is linked to the model in the directive's isolated scope. Changes to one model affects the other, and vice versa.
I would prefer using bindToController property definition in the directive. When set to true in a directive with isolated scope that uses controllerAs, the component’s properties are bound to the controller rather than to the scope.
That means, Angular makes sure that, when the controller is instantiated, the initial values of the isolated scope bindings are available on this, and future changes are also automatically available.
Check the Below sample fiddle example for more understanding
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function($scope) {
$scope.change = function() {
$scope.fullname = 'Keshan';
}
$scope.reset = function() {
$scope.fullname = 'Fill Your Name';
}
});
myApp.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
name: '='
},
controller: function($scope) {
this.name = 'Fill Your Name';
},
controllerAs: 'ctrl',
bindToController: true,
template: '{{ctrl.name}}',
};
});
<script src="https://code.angularjs.org/1.3.7/angular.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<button ng-click="change()">Change</button>
<button ng-click="reset()">Reset</button>
<my-directive name="fullname"></my-directive>
</div>
Further Reading
Is there a way to find the closest parent scope inside a directive that is a controller scope?
Use case.
The important part is at line 212 is the js file.
The template I render from the directive has scope expressions passed in the directive attribute and I need to link them to the controller scope.
Because the directive has an isolated scope, I link it to scope.$parent because in this case the parent is the controller.
But this might not always be the case, so how can I find the closest parent controller scope.
Can I loop trough the scope checking if it's a controller scope?
var template = angular.element(html);
var linkFn = $compile(template);
var child = linkFn(scope.$parent); //HERE IS THE PROBLEM
$(element).append(child);
You may try to use jqLite/jQuery methods from within your directive to traverse DOM tree up and locate the required parent (element.parent(), etc). Then you can get the scope object related to this parent by calling scope() method on it, which you should be able to pass as an argument to your compilation function.
It's not the cleanest approach, but you can always pass in an object to the directive's scope.
angular.app('myApp', [])
.controller('myCtrl', [function() {
var self = this;
self.directiveConfig = {
method1: function() { /* do something cool */ },
method2: function() { /* do something cool */ }
};
}])
.directive('myElem', [function() {
var myElem = {
restrict: 'AE',
controller: function() { /* some custom controller */ },
link: function(scope, element, attributes, ctrl) {
/**
* scope.config will now be tied to self.directiveConfig
* as defined in the controller above
*/
},
scope: {
config: '='
}
};
});
Then, assuming your controllerAs is set to ctrl, you could do.
<my-elem config="ctrl.directiveConfig"></my-elem>
A better solution would be to put any functions you need to use like that inside a service that can be reused.
See there are many ways . One of the way could be , you could check for an variable that is defined on that controller scope like this
if(angular.isDefined(scope.$parent.variableName))
{
// controller is defined
}
else
{
// controller is not defined
}
If this is defined then it is your controller , otherwise something else. Second way would be to check parent scope of the parent like this
if(angular.isDefined(scope.$parent.$parent)) {
//controller is defined
}
else
{
//controller is not defined
}
As every controller will have a parent as $rootScope or $scope of some other controller . That means if it is not a controller then this would result in undefined and will go else condition.
requiring ngController seems to be the best solution.
.directive("mobilitTree", function() {
return {
...
require: "ngController",
...
link: function(scope, element, attrs, ngCtrl) {
...
}
};
})
.controller("AppController", function($scope, ...) {
//Only variable applied on this are public
this.controllerOnSort = function(...) { ... };
});
I'm trying to figure out how to make a child directive communicate with it's parent directive
I basically have this html markup
<myPanel>
<myData dataId={{dataId}}></myData>
</myPanel>
In the myData directive, if there is no data available, I want to hide the myPanel.
In the myData directive controller I've tried
$scope.$emit('HideParent');
And in the myPanel controller I've tried
$scope.$on('HideParent', function () { $scope.hide = true; });
And also
$scope.$watch('HideParent', function () { if (value) { $scope.hide = true; }});
In either situation, the myPanel directive isn't receiving the $emit
You may create controller in myPanel directive.
Then require this controller in myData directive. And when child directive has no data, call controller method to hide parent.
For example in you parent (myPanel) directive:
controller: function($scope, $element){
$scope.show = true;
this.hidePanel = function(){
$scope.show = false;
}
}
In myData directive require this controller:
require:'^myPanel'
And then, call controller function when you need
if (!scope.data){
myPanelCtrl.hidePanel();
}
Look this Plunker example
The answers above where the parent directive's controller is required is a great one if there truly is a dependency. I had a similar problem to solve, but wanted to use the child directive within multiple parent directives or even without a parent directive, so here's what I did.
Your HTML.
<myPanel>
<myData dataId={{dataId}}></myData>
</myPanel>
In your directive simply watch for changes to the attribute.
controller: function ($scope, $element, $attrs) {
$attrs.$observe('dataId', function(dataId) { $scope.hide = true; } );
}
It's explicit and simple without forcing a dependency relationship where one may not exist. Hope this helps some people.
I have declared a variable in parent scope in one of my controllers.
$scope.myValue = true;
This value is fetched in the custom directive easily. But when the value is updated in the parent controller it is not reflected in the custom directive.
My question is how can we let the directive know that the "myValue" is changed?
If the info is not suffice kindly let me know.
In your parent controller, upon changing the value of the variable, you can broadcast an event like this -
$scope.$broadcast('valueChanged', {
val: $scope.myValue // pass in any object you want
});
And then in your child controllers, handle it like -
$scope.$on('valueChanged', function (event, data) {
console.log(data); // get the same object here
});
$broadcast fires the event down, so this should work in your case.
You can watch the value inside the directive, like this:
app.directive('directive', function() {
return {
scope: true,
link: function($scope){
$scope.$watch('$parent.myValue', function(newVal){
console.log(newVal);
});
}
};
});
Link demo: https://jsfiddle.net/mzrja04L/