I have created two directives, one of them is used within the other and it has been created just for the matter of seperation of concerns.
<div class="docinputContainer">
<textarea></textarea>
<easy-table></easy-table>
</div>
I have omitted the scope property on both of them, therefore i would have thought that i could bind a keydown eventon my parent directive, which then could call a $scope method from the child directive:
// parent
element.bind('keydown',function(e){
$scope.save();
});
// easyTable
$scope.save = function(){
// ...
}
However, this method is unknown in my parent directives $scope.
Now i have some questions:
Why is this? Shouldn't both directives share a scope?
What would be the best solution to this?
I know i could do one of the following:
Broadcast an event
Use a shared service to trigger an event
Concatenate both directives
I would prefer to share the scope, though. Any help is appreciated
Solution
I bypassed my issue by binding the element to a scope property on the parent directive, then binding the keydown event in easyTable itself, instead of the parent one.
Related
I've set up a directive that will take in a function from the parent controller as one of it's attributes. This function will be replaced by the directives controller and will be accessible by the parent controller by using the passed function. The issue I'm running into is when placing an ng-if attribute on the directive the passed in function is not being replaced anymore. What causes this and how would I go about fixing this issue? I've attached an example
https://jsfiddle.net/dh7jt1zg/1/
<div class="parent" ng-controller="pCtrl">
<h3>Parent - with ng-if</h3>
<div>{{parentHelloWorld()}}</div>
<child ng-if="testBool" rep-fun="parentHelloWorld"></child>
</div>
<br/>
<div class="parent" ng-controller="pCtrl2">
<h3>Parent2 - without ng-if</h3>
<div>{{parentHelloWorld()}}</div>
<child rep-fun="parentHelloWorld"></child>
</div>
Use ng-show instead of ng-if.
ng-if causes element to remove from the DOM.
I think you'd be better off to create a service instead of replacing the function in the child controller. That seems kind of messy to me.
Personally, I'd use a service to register the function and then you can use that same service from the parent controller to call it.
Could you please explain me one thing. Imagine we have directive called "myDirective". Here is html:
<div my-directive>
</div>
When we remove this div from DOM, will angular destroy scope of myDirective and its watchers or I have to listen the event "$destroy" on div DOM element and inside the listener to call scope.$destroy?
Usually when you remove a div from DOM, angularJS automatically deallocates events and destroys the scope, even if you don't listen to $destroy event.
So why you should use $destroy? You should use it to manually deallocate some non angularJS events and plugins (e.g. if you create a 'Kendo Grid' inside myDirective you need to destroy it inside the $destroy function using the plugin function to destroy it).
I suggest also to set all references to objects in the scope to null (best practice).
I'm trying to understand how Angular data binding works. I have created this sample that displays {{values.count}} inside a span element:
<body ng-app="testApp" ng-controller="testCtrl as values">
<span>Count = {{values.count}}</span>
<button id="incr">++</button>
</body>
<script>
testApp = angular.module("testApp", []);
testApp.controller("testCtrl", function () {
var values = this;
values.count = 5;
$("#incr").on('click', function () {
values.count += 1;
});
});
</script>
If I click on the button, values.count is incremented, but the span is not updated. I can get it to work if I place a ng-click="values.Increment()" on the button and create a values.Increment method in the controller, but what is the difference that makes one work and the other not?
You are doing it the wrong way. When using angular, you don't need to bind event handlers/access DOM elements for this purpose. Use built-in ng-click directive.
<button ng-click="values.incr()">++</button>
and
values.incr = function(){
values.count++;
}
You could even do it inline, <button ng-click="values.count = values.count+1">++</button>
The reason why your code did not update DOM is because of the way angular works. Even though you are incrementing the value inside the manually bound event handler angular is unaware of the update and it does not update DOM binding with the new value. Though a bad practice in your specific case you can achieve that by doing $scope.$apply() (but ofcourse you need to inject $scope in your controller) as well inside the event handler to manually invoke the digest cycle. General thumb rule when working with angular is the controller do not access DOM directly, Instead it just sets up data for the view and with the help of bindings and other built-in or custom directives you get the intended behavior in your application.
Read about scope life cycle and digest cycle
Background:
I am using a custom CMS where I have limited access to the code base. So, in a few cases I plan to make some DOM manipulations using JavaScript.
Problem:
I have a container directive and the container has plain old HTML items, but am not able to mark the items as being directives from the server side. Also, the plain old HTML items contain sub-content that are directives.
Example:
Here is the before:
DIV[container-directive]
DIV.some-item-in-html
DIV[some-directive-in-the-content]
DIV.some-item-in-html
DIV[some-directive-in-the-content]
...
Here is what the DOM should look like afterwards:
DIV[container-directive]
DIV[container-item] <-- This is what needs to be inserted
DIV.some-item-in-html
DIV[some-directive-in-the-content]
DIV[container-item] <-- This is what needs to be inserted
DIV.some-item-in-html
DIV[some-directive-in-the-content]
...
Question:
Does anyone have suggestions on the best approach to injecting DOM element that are directives in-between a nesting of directives using JavaScript?
Some thoughts:
I think manipulate the DOM in advance of the compilation by angular, but I wonder if there is a way to do this within Angular's framework.
Another option is from the container directive's post-linking function, I could wrap the HTML items in directive elements called "container-item" and then $compile the items manually. So, I tried this but I get an error related to the items already having directives inside with transcluded content. Something about the "ngTransclude" being unexpected. I think this is related to the inner directives already having been processed.
I would go with your first option and manipulate the DOM ahead of angular compilation.
You can do this within a directive that contains the elements that you want to manipulate.
For example:
app.directive('body', function() {
return {
restrict: 'E',
compile: function(element, attr) {
// find the inner element and wrap it
$('.some-item-in-html', element).wrap('<div class="container"></div>');
}
}
});
Parent directives are always compiled before child directives, so you can change the DOM of the children within the compile property, and not have to worry about recompiling or re-linking directives.
[EDIT]
Thanks to Biagio for the following edit.
This method shouldn't be used with a directive with a template because the element would be assigned to the template and not the child elements.
Another alternative is doing the DOM manipulation in a function that runs at the start of the angular lifecycle.
For example:
app.run(function(){
// find the inner element and wrap it
$('.some-item-in-html').wrap('<div class="container"></div>');
});
I have a directive with some boilerplate html, including a menu toggle button.
Within that directive (transcluded) is one of a number of different nested directives, each with it's own menu.
The outer directive has an isolated scope and the inner directive has no scope specified.
When I click the menu toggle button on the outer directive, I want to be able to show/hide the menu contained in the inner directive.
How do I communicate the change in the toggle value from the outer directive to the inner directive?
I have tried broadcasting the change in the outer directive and listening for it in the inner directive, but that doesn't seem to work.
I have tried requiring the outer directive in the inner directive - I can see the value of the toggle there, but don't see a way of watching it for changes.
Here's the link function from the outer directive:
link: function($scope, $element, $attrs){
var menuCollapsed = true;
$scope.toggleMenu = function(){
menuCollapsed = !menuCollapsed;
console.log("ToggleMenu",menuCollapsed);
$scope.$broadcast('menuCollapsed', menuCollapsed);
};
}
and the inner directive contains this:
link: function($scope, $element, $attrs, widgetCtrl){
$scope.$on('menuCollapsed', function(event, args){
console.log('menuCollapsed', event, args);
});
The log in the outer directive displays, but not the one in the inner directive.
if you use scope.emit the event will bubble up if you use scope.broadcast the event will be broadcasted down your tree.
I persoanly prefer events over watchers how ever if your child directive is a direct child of your parents directive scope, you can use standard data binding in your child dierctive attributes.
to require a parent directive in a child directive, the parent directive has to expose its API through its controller function.
those are hints for more detailed help i would need to see some code