Directive communication - sharing a reference to an inner HTML element - javascript

I want to find a way to do clean communication between my two sibling directives. I want to implement "insertAtCaret" functionality for a textarea in one directive, to be called from another.
<text-holder ng-model='model.text' />
<text-inserter>
<a ng-click='insert("hello")'>Hello</a>
</text-inserter>
text-holder turns into something like this:
<div class='my-class'>
<h3>Enter some text:</h3>
<textarea ng-model='ngModel'></textarea>
</div>
The text-inserter needs to insert stuff into that textarea - what's the cleanest angular-ish way to allow that communication? I want to be able to support multiple instances of that on the page. Should I just create a unique id for each one from a shared service? It seems a little unclean.

You can :
Wrappe your directive in outer DOM element.
create a communication directive on this element.
Use the controller of this directive as an API for communication between the two directives.
Use require from the two directive, to, set, the text.
<div text-com-directive>
<text-holder ng-model='model.text' />
<text-inserter>
<a ng-click='insert("hello")'>Hello</a>
</text-inserter>
</div>
Directive :
directive('textComDirective', function(){
return {
scope:{},
controller: function($scope){
// create functions that will be used to set/use the text inserter.
}
}
});

The only chain between two directives is a variable that is supposed to be updated, this is also used by both directive. The text-inserter directive is sort of like choosing the method to be executed to the text-holder
html
<text-holder ng-model='model.text'></text-holder>
<text-inserter>
<a ng-click='model.insert("hello")'>Hello</a>
</text-inserter>
script.js
var app = angular.module('testapp',[]);
app.controller('appController', function ($scope) {
$scope.model = {text: 'sample', insert: function(a){$scope.model.text = a}};
})
app.directive('textInserter', function () {
return {
restrict: 'E',
trasclude: true // important to keep the content that is defined outside of directive
}
});
Sample
The insert function is set in the controller that is holding the variable to pass to the directive, this way helps us to easy understand what logic should be applied and is going to happen for the model variable in the initiated scope it self.
The more benefit is you can situational change the behavior for some specific instance.

Related

Why $scope.getList() gets invoked on state change of $scope.showList?

In the below code,
<label>
<input type="checkbox" ng-model="showList">
Show unordered list
</label>
<ng-include src="getList()"></ng-include>
$scope.getList() gets invoked on change of $scope.showList by check or uncheck, where $scope.showList is used as,
app3.controller('gListCtrl', function($scope){
$scope.getList = function(){
return $scope.showList ? "ulgrocerylist.html" : "grocerylist.html";
};
});
Why $scope.getList() gets invoked on change of state of $scope.showList?
Similar code,
<p>
<button ng-disabled="disableButton">Button</button>
</p>
<p>
<input type="checkbox"
ng-model="disableButton">DisableButton
</p>
make sense to me, because disableButton state is changing, so button gets disabled or enabled due to two way binding.
First of all, You're a little bit incorrect on your question. The $scope.getList() function gets invoked not only on a state change, but on every digest cycle. Let me explain.
Because the framework has absolutelly no clue what code is in the getList function. It does not statically analize your code, since it would be both very hard and very inneficient. Due to the nature of how you can use AngularJS, you could be changing the output of getList according to a variable in a completely different controller, service, scope, etc. Thus, this output might need to be rerendered upon every digest cycle. AngularJS recognizes this, because you have the function call in your template and calls it on every digest to check whether it needs to swap out the template.
Consider this application structure:
<div ng-app="testTest">
<script type="text/ng-template" id="template.html">
<div>Hello world!</div>
</script>
<div ng-controller="templateViewer">
<div>
<div ng-include="content()"></div>
</div>
</div>
<div ng-controller="templateChanger">
<button ng-click="handleClick()">Show / hide content</button>
</div>
</div>
and this code to wire it:
var app = angular.module('testTest', []);
app.factory('template', function() {
return {
show: false
};
});
app.controller('templateChanger', function($scope, template) {
$scope.handleClick = function() {
// toggle showing of template
template.show = !template.show;
};
});
app.controller('templateViewer', function($scope, template) {
// if the result of this function is not re-evaluated on every digest cycle,
// Angular has no idea whether to show or hide the template.
$scope.content = function() {
return template.show ? 'template.html' : '';
};
});
So, the framework needs to rely on this constant re-evaluation of properties and functions that are binded to the templates in the HTML. Since all the data structures that you use are plain javascript objects, and you don't explicitly tell the framework that something in your viewmodel has changed (as you would do by invoking set() methods on your models in other frameworks, such as Backbone or Ember) – angular has to check all variables and re-run all the functions that could possibly change the look of your view, and ng-include is one of these cases.
You can use watcher or observe events on showList variable value changes.

AngularJS - Setting a controller's $scope variable in a custom directive that accesses the DOM

I just answered a question here
How i send value when i click button to server and popup directive
His code looked like this
<div ng-controller="IndexController">
<label id="number" >5</label>
<input type="button" ng-click="getUserDetails()" >
</div>
He was trying to access the value of the label element with id #number.
I understand that in Angular, DOM manipulation and querying should be done in a custom directive, and not inside a controller. I therefore gave this solution.
Since you want to access the value of a number, you are interacting with the DOM. The best solution for this is creating custom directives. Here is an example.
angular.module('myApp', [])
.directive('getValue', function({
restrict: 'A',
link: function(scope, elem, attrs) {
elem.bind("click", function() {
scope.value = elem.prev().text();
});
}
});
I know that in a directive's link function, you pass in scope, element and attrs as a param. In this line, I'm trying to set a scope variable to the label's value.
scope.value = elem.prev().text();
But I have a suspicion that I'm not doing it correctly. Since directives are the way to go, how can I do this.
I also checked this as a reference
Easiest way to pass an AngularJS scope variable from directive to controller?
I've read about the controller function inside directives, but I need the link function for getting the value from the DOM.
There's no need for a custom directive for that. ng-click can work:
$scope.getUserDetails = function(e){
$scope.value = e.target.previousElementSibling.innerHTML;
}
HTML
<div ng-controller="IndexController">
<label id="number" >5</label>
<input type="button" ng-click="getUserDetails($event)" >
</div>
$event

Using controller as syntax only updates one of the scopes

I'm a bit confused when it comes to the controller as syntax as I've never worked with it before. I'd like to know the correct way to fix this. Haven't been able to find a similar problem when searching.
I have a menu and a button which toggles the menu. The menu has its own scope and the button has another, as they live in two separate files and containers.
When I click the button it only updates the nav.isActive within the button scope. I created a service for storing the state, which I shouldn't have to do when I think about it.. Should I? Because the only way to watch if that value changes is with a watcher, which would require me to use $scope since this doesn't have the $watch function, however, I'd like to avoid this as much as I can as it will affect performance which is important in this project.
What is the correct way of updating a "scope" variable from another "scope" when using the controller as syntax?
Controller:
nav.controller('NavCtrl', ['navState', function(navState) {
var nav = this;
nav.menu = [
{icon: 'color_lens', href: ''},
{icon: 'color_lens', href: 'about'},
{icon: 'color_lens', href: 'contact'},
{icon: 'color_lens', href: 'logout'},
{icon: 'color_lens', href: 'faq'}
];
nav.toggleMenu = function() {
nav.isActive = navState.checkState();
}
}]);
The service for passing the value from one scope to the other:
nav.service('navState', [function() {
var isActive = false;
this.checkState = function() {
isActive = !isActive;
return !isActive;
}
}]);
The menu markup (menu.html):
<nav class="si-wrapper si-dark" ng-controller="NavCtrl as nav" ng-class="{active: nav.isActive}">
<ul class="si-list">
<li class="si-item" ng-repeat="item in nav.menu">
<a href="#/{{item.href}}">
<div class="si-inner-item">
<i class="material-icons md-36" ng-bind="item.icon"></i>
</div>
</a>
</li>
</ul>
<h1>{{nav.isActive}}</h1> <!-- This doesn't change -->
</nav>
The button which toggles the menu (header.html):
<div ng-controller="NavCtrl as nav">
<button ng-click="nav.toggleMenu()">Toggle</button>
<span>{{nav.isActive}}</span> <!-- This updates however -->
</div>
Your problem is that nav.isActive is not set for one of the controllers. Basically whenever you use ng-controller a new controller (and $scope) is created. So for each of your controllers, $scope.isActive needs to be set for the related view to reference it.
In your posted code, isActive is only set when toggleMenu() is run, which happens only in header.html.
In order to get your code to work, simply set isActive on controller load. For example:
nav.controller('NavCtrl', ['navState', function(navState) {
// put isActive on the scope on controller load
this.activeState = navState.getState();
nav.toggleMenu = function() {
navState.toggleState();
};
}]);
You need to make your service better by separating state access and state manipulation. Also wrapping it in a container object will ensure you won't have any scope hierarchy issues.
nav.service('navState', [function() {
var state = {
isActive: false
};
this.toggleState = function() {
state.isActive = !state.isActive;
};
this.getState = function(){
return state;
};
}]);
Then you need to use `activeState.isActive' inside your view.
Now you will have a service with a shareable state, and two controllers that both reference the same service on load. Then inside your menu view, when you toggle the state, both controllers scopes are updated.
From the angularjs docs:
When a Controller is attached to the DOM via the ng-controller directive, Angular will instantiate a new Controller object, using the specified Controller's constructor function. A new child scope will be created and made available as an injectable parameter to the Controller's constructor function as $scope.
Additionally, the ng-controller directive has a scope that inherits from its parent scope. So, you can also define the shared data in the parent. It will be accessible on the 'NavCtrl' scope. But since you want to use the controller as synax one way would be to share the data with a help of a service.
However, I don't like the approach with the 'ng-controller' directive. The other solution is to replace it with a hierarchy of directives where the child directives will require the parent one. Also use 'controllerAs' property there.

Adding an element with directives in Angular

I am adding an element dynamically in Angular. The code is as follows.
myelement.after('<input ng-model="phone" id="phone">');
The element gets added and all works fine. However I think I'm missing a step and Angular does not recognize the new element because when I collect data later, the value of the dynamically added element is not defined.
The reason for adding the element on fly is that the number of inputs is not known from the beginning and user can add as many of them as they want. The actual code is like:
myelement.after('<input ng-model="phone"' + counter + ' id="phone' + counter + '">');
I'm sorry, I should have provided complete sample from the beginning. Please see the following jsfiddle, add some phones (with values) and list them: http://jsfiddle.net/kn47mLn6/4/
And please note that I'm not creating any new directive. I'm only using Angular standard directives, and I prefer not to create a custom directive for this work (unless required).
Assuming that you are adding element to the DOM using directive. You can use built in angular service $compile.
Firstly inject the $compile service to your directive. Then in your link function of the directive, get your myElement after which you want to append your element. Then create your element using angular.element(). Then compile it using $compile service and pass the scope in which you are in now.(Here this scope is the directive scope). After getting the compiled dom element you can just append it after your myElement.
here is an example about how you can add element dynamically from directive:
var elementToBeAdded = angular.element('<input ng-model="phone" id="phone">');
elementToBeAddedCompiled = $compile(elementToBeAdded)(scope);
myElement.append(elementToBeAddedCompiled);
If you add your element using $compile service in your directive, angular will recognize your dynamically added element.
I was trying to accomplish this without using directives, but it seems the best way (and probably the only proper way) of adding multiple elements to DOM in Angular is by defining a custom directive.
I found a very nice example here http://jsfiddle.net/ftfish/KyEr3/
HTML
<section ng-app="myApp" ng-controller="MainCtrl">
<addphone></addphone>
<div id="space-for-phones"></section>
</section>
JavaScript
var myApp = angular.module('myApp', []);
function MainCtrl($scope) {
$scope.count = 0;
}
//Directive that returns an element which adds buttons on click which show an alert on click
myApp.directive("addbuttonsbutton", function(){
return {
restrict: "E",
template: "<button addbuttons>Click to add buttons</button>"
}
});
//Directive for adding buttons on click that show an alert on click
myApp.directive("addbuttons", function($compile){
return function(scope, element, attrs){
element.bind("click", function(){
scope.count++;
angular.element(document.getElementById('space-for-buttons')).append($compile("<div><button class='btn btn-default' data-alert="+scope.count+">Show alert #"+scope.count+"</button></div>")(scope));
});
};
});
//Directive for showing an alert on click
myApp.directive("alert", function(){
return function(scope, element, attrs){
element.bind("click", function(){
console.log(attrs);
alert("This is alert #"+attrs.alert);
});
};
});
in the angular mind you should manipulate the dom from the JS code.
and use ng-* directive
So i don't know you code but i think you just need to do something like :
View
<button ng-click="showAction()">Show the phone input</button>
<input ng-show="showPhone" ng-model="phone" id="phone">
Controller
app.controller('ctrl', [function(){
$scope.showPhone = false;
$scope.showAction = function() {
$scope.showPhone = true;
};
}]);
Outside of Angular you would need to use the event handler to recognize new elements that are added dynamically. I don't see enough of your code to test, but here is an article that talks about this with $.on():
Jquery event handler not working on dynamic content
Here is a good article that will help with directives and may solve your problem by creating your own directive:
http://ruoyusun.com/2013/05/25/things-i-wish-i-were-told-about-angular-js.html#when-you-manipulate-dom-in-controller-write-directives

AngularJS : DOM manipulation of ng-repeat elements in a directive

I am new to angularJS. I am trying to implement the a simple functionality wherein there are a number of checkbox options and one specific function needs to be called on click of these checkbox options something similar to
$(parentDiv).on("click",'input[type="checkbox"]', functionToBeCalled});
I am trying to use a directive for this section with content similar to
<div>
<span ng-repeat="subsection in section"><input type="checkbox" value="subsection.name"></input> {{subsection.name}} </span>
</div>
Directory
app.directive('myDir', function() {
return {
restrict: 'AE',
replace: true,
templateUrl: 'filter.html',
link: function(scope, elem, attrs) {
elem.find("input").bind('click', function() {
//do some data manipulation
});
});
};
});
When i use the link function I find that if I use elem.find('input[type="checkbox"]) gives me an empty object so it is not compiled before that. Kindly let me know how to address this situation. I did a bit of a research and found out about compile function which manipulates DOM before linking,but i am not able to think of the approach ahead. Any help will be appreciated. Thanks in advance.
Use ngClick or ngChange Directive inn angularjs
<div>
<span ng-repeat="subsection in section">
<input type="checkbox" value="subsection.name" ng-click="myFunc(subsection.name)" /> {{filter.name}} </span>
</div>
here in the example i used ng-click = "myFunc" and pased the value in that function
It might help for us to know how you directive looks like and specifically what element that directive is attached to, since you're speaking about elem.
Nevertheless, if you just need functions to be called when the checkbox is clicked, you can use built-in angular functions for this:
ngChange
ngClick

Categories