data-ng-click event binding to the DOM - angularjs

I am new to angularjs, I have code in which on clicking the 'button' one new row gets inserted with one 'new button', now in turn that 'new button' also should have ng-click event. The problem is when I try doing this, the the dynamic row with new button gets added but the new button is not getting the click event attached to it. After some research I found that I should compile the element string before adding to the DOM with the help of "$compile" service of the angularjs. but then the browser throwing error saying that '$compiler is not a function'... Please help. Thank you..!!
following are the code snippets
jsp page code
<td>
<button type="button" id="clickButton" data-ng-click="insert()"
class="btn btn-sm btn-default">
<i class="fa fa-plus fa-lg"></i>
</button>
</td>
angularjs controller code
$scope.insert = function($compile){
var tableRow ="<tr data-ng-repeat='c in ctrl.client.clientOwnerVOList' id='insertionRow"+count+"'>"+
"<td>"+i+"</td>"+
"<td class='col-lg-3'><input type='Text' class='form-control' data-ng-model='c.clientOwnerName' name='clientOwnerName{{$index + 1}}' id='Name'></td>"+
"<td class='col-lg-4'><input type='Email' class='form-control' data-ng-model='c.clientOwnerEmail' name='clientOwnerEmail{{$index + 1}}' id='Email'></td>"+
"<td class='col-lg-3'><input type='Text' class='form-control' data-ng-model='c.clientOwnerPhone' name='clientOwnerPhone{{$index + 1}}' id='PhoneNo'></td>"+
"<td><button type='button' data-ng-click=insert() class='btn btn-sm btn-default'><i class='fa fa-plus fa-lg'></i></button></td>"+
"<td><button type='button' class='btn btn-sm btn-default' onClick=$(this).closest('tr').remove();><i class='fa fa-trash fa-lg '></i></button></td>"+
"</tr>";
var newTableRow = $compile(tableRow)($scope);
$("#insertionRow").append(newTableRow);
i++;
};

You can create a directive that uses compile:
app.directive('dynamic', [ '$compile',
function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
}]);
Then in your html:
<div dynamic="tableRow"></div>
... wherever you want the table row to show.

Why are you passing $compile as the function argument?You should inject $compile service just as you inject $scope , i.e in your controller/directive.
That might be the reason , it is throwing $compile is not a function error.

Related

Angular ng-click function requiring two clicks to take effect

I have the following directive
app.directive('replybox', function ($timeout, $window, $compile, $sce) {
var linkFn = function (scope, element, attrs) {
var exampleText= element.find('p');
var btn = element.find('button');
var windowSelection="";
scope.okOrCancel="None";
exampleText.bind("mouseup", function () {
scope.sel = window.getSelection().toString();
windowSelection=window.getSelection().getRangeAt(0);
if(scope.sel.length>0) {
scope.showModal = true;
scope.$apply();
}
});
btn.bind("click", function () {
if(scope.okOrCancel=='ok'){
range = windowSelection;
var replaceText = range.toString();
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = '<poper>' + replaceText + '<button type="button" class="btn btn-danger btn-xs">×</button></poper>';
var frag = document.createDocumentFragment(), child;
while ((child = div.firstChild)) {
frag.appendChild(child);
}
$compile(frag)(scope);
range.insertNode(frag);
scope.showModal=false;
}
if(scope.okOrCancel=='cancel'){
scope.showModal=false;
}
scope.selection="None";
scope.okOrCancel='None';
});
};
return {
link: linkFn,
restrict: 'A',
scope: {
entities: '=',
selection:'='
},
template: `<ng-transclude></ng-transclude>
<div class="modal fade in" style="display: block;" role="dialog" ng-show="showModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{{sel}}
</div>
<div class="radio">
<div ng-repeat="x in entities">
<div class="radio">
<label>
<input type="radio" name="choice" ng-model="$parent.selection" ng-value = "x">
{{x}}
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="okOrCancel='ok'">
Ok
</button>
<button type="button" class="btn btn-primary" ng-click="okOrCancel='cancel'">
Cancel
</button>
</div>
</div>
</div>
</div>`,
transclude: true
};
});
So there is a modal in the template which contains an "Ok" and a "Cancel" button. There is an ng-click on these buttons which sets scope.okOrCancel to the appropriate value. btn binds to a button click and performs different actions depending on the state of scope.okOrCancel. When the "Ok" button is clicked everything works as expected. But the "Cancel" button requires two clicks in order for the modal to dissappear. I would think this would happen immediately within
if(scope.okOrCancel=='cancel'){
scope.showModal=false;
}
Can anyone tell me why the cancel button requires two clicks to close the modal?
Currently you have a mix of jQuery and angularjs for your ok and cancel click. Probably that is the reason to require two clicks to take effect.
If I were you, I would have write click like below:
Template:
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="okClick()"> Ok </button>
<button type="button" class="btn btn-primary" ng-click="cancelClick()"> Cancel </button>
</div>
In JS:
scope.okClick = function() {
range = windowSelection;
var replaceText = range.toString();
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = '<poper>' + replaceText + '<button type="button" class="btn btn-danger btn-xs">×</button></poper>';
var frag = document.createDocumentFragment(), child;
while ((child = div.firstChild)) {
frag.appendChild(child);
}
$compile(frag)(scope);
range.insertNode(frag);
scope.showModal=false;
}
scope.cancelClick = function() {
scope.showModal=false;
}
scope.selection="None";
scope.okOrCancel='None';
I hope this helps you!
Cheers
Completely agree with varit05's answer. Most likely it's because you do not trigger digest cycle in the click event handler. But in any way, the point is: it's not very good idea to mix jquery and angular stuff, unless: a) you absolutely sure it's necessary; b) you understand very well what you're doing and why; otherwise it will lead to such an unexpected consequences.
Just another a small addition. Another problem is here:
$compile(frag)(scope);
range.insertNode(frag);
The correct approach would actually be to insert new nodes into real DOM and only then $compile() them. Otherwise, any directives with require: "^something" in the DOM being inserted will fail to compile because they would not be able to find necessary controllers in upper nodes until new nodes actually make it to the "main" DOM tree. Of course, if you absolutely sure you don't have that then you can leave it as is and it will work... But then the problem will just wait out there for its "finest hour".

How to use index of each item when item is written in directive?

I just want to point out that this is my first attempt to create simple app in AngularJS.I have a table , and in each row I'm displaying task which contains Index,Name,Activity,Time properties and also edit and delete buttons.Everything is working perfect.
<table class="table table-striped " style="width: 100%">
<tr ng-repeat="task in tasks" >
<td>{{$index}}</td>
<td>{{task.name}}</td>
<td>{{task.activity}}</td>
<td>{{task.time}}</td>
<td ><button type="button" ng-click="editRow($index)" class='btn btn-default '>Edit</button></td>
<td ><button type="button" ng-click="removeRow($index)" class='btn btn-default'>Delete</button></td>
</tr>
Index is very important to me ,because I'm accessing current task by index when edit or delete button is clicked.
Now, I'm trying to modify my code, I'm reading directives, and had idea to put everything from table row into a template inside directive, and than to call that directive in ng-repeat.
Something like this :
<tr ng-repeat="task in tasks" task-item = "task" >
</tr>
and directive :
app.directive('taskItem', function() {
return {
scope:{
taskItem: '='
},
restrict: 'A',
template: "<tr><td>{{$index}}</td>" +
"<td>{{taskItem.name}}</td>" +
"<td>{{taskItem.activity}}</td>" +
"<td>{{taskItem.time}}</td>" +
"<td><button type='button' ng-click='editRow($index)' class='btn btn-default '>Edit</button></td>" +
"<td><button type='button' ng-click='removeRow($index)' class='btn btn-default'>Delete</button></td>"+
"</tr>",
replace: false
};
});
Now I have issue , because index for each task is 1 and also edit and delete button click is not working .Maybe I have to write this template different, or try other approach.
So if anyone has any idea, please feel free to add comment.Thank You.
You can also pass in the index to your directive.
<tr ng-repeat="task in tasks" task-item="task" index="$index"></tr>
Then you can access that by adding it to the scope of your directive:
app.directive('taskItem', function() {
return {
scope:{
taskItem: '=',
index: '#'
},
restrict: 'A',
template: "<tr><td>{{index}}</td>" +
"<td>{{taskItem.name}}</td>" +
"<td>{{taskItem.activity}}</td>" +
"<td>{{taskItem.time}}</td>" +
"<td><button type='button' ng-click='editRow(index)' class='btn btn-default '>Edit</button></td>" +
"<td><button type='button' ng-click='removeRow(index)' class='btn btn-default'>Delete</button></td>"+
"</tr>",
replace: false
};
});
I've found working solution :) Apparently , there was a problem with something called "isolate scope" (don't judge me if I'm not using some of term correctly , I'm completely new to Angular )
So , I've removed scope part from directive
app.directive('taskItem', function() {
return {
restrict: 'A',
template: "<tr><td> {{$index}} </td>" +
"<td>{{task.name}}</td>" +
"<td>{{task.activity}}</td>" +
"<td>{{task.time}}</td>" +
"<td><button type='button' ng-click='editRow($index)' class='btn btn-default '>Edit</button></td>" +
"<td><button type='button' ng-click='removeRow($index)' class='btn btn-default'>Delete</button></td>"+
"</tr>",
replace: false
};
});
and made minor change in ng-repeater
<tr ng-repeat="task in tasks" task-item >
</tr>
And now , index displayed is different for each item, and also I can use both edit and delete button .

AngularJS - $emit fire before $on

I am working with AngularJS framework. I wrote a few directives with and without their own controller and they sometimes include each other.
For every course, I use my own directive to print its informations
<li ng-repeat="course in courses" on-courses-loaded="fetch_subscribed">
<course-info course="course"></course-info>
</li>
The 'on-courses-loaded' attribute is looking for the last element, I wrote it to do things when ng-repeat is over : a few data are prepared and signal is emitted for every function register as $rootScope.$on('courses_available', function() {}) (scope < actual scope)
angular.module('app').directive('onCoursesLoaded', [
'$rootScope', function ($rootScope) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true)
{
element.ready(function() {
// Prepare things, when done return promise and then ->
$rootScope.$broadcast('courses_available');
});
}
}
}
}]);
In course info directive, there is a call to another directive
<subscribe code="course.code" triggered="true"></subscribe>
With this code
<div ng-show="available">
<button type="button" class="btn btn-success" ng-hide="subscribed" ng-click="subscribe()">
<span class="glyphicon glyphicon-ok"></span> Subscribe
</button>
<button type="button" class="btn btn-danger" ng-show="subscribed" ng-click="unsubscribe()">
<span class="glyphicon glyphicon-remove"></span> Unsubscribe
</button>
</div>
And THIS link function
if (scope.triggered === true)
{
console.log('wait for emit');
$rootScope.$on('courses_available', function() {
internals.getSubscribed();
});
}
else
{
console.log('do not wait for emit');
scope.$watch('code', function(value) {
internals.getSubscribed();
});
}
The biggest issue is that signal is emitted before subscribe directive has set its $rootScope.$on('courses_available', ..)
Here you can find HTML pseudo code expanded
<li class="list-group-item" ng-repeat="course in courses | filter: search" on-courses-loaded="fetch_subscribed">
<!--<course-info course="course"></course-info> IS NEXT DIV-->
<div>
<!--<subscribe code="course.code" triggered="true"></subscribe> IS NEXT DIV-->
<div ng-show="available">
<button type="button" class="btn btn-success" ng-hide="subscribed" ng-click="subscribe()">
<span class="glyphicon glyphicon-ok"></span> Subscribe
</button>
<button type="button" class="btn btn-danger" ng-show="subscribed" ng-click="unsubscribe()">
<span class="glyphicon glyphicon-remove"></span> Unsubscribe
</button>
</div>
</div>
I need to prepare data and emit signal after ng-repeat
While 1., every content from ng-repeat must subscribe to $rootScope.$on() and before emit.
If you have any tips, thank you all.
You can detect finish event of ng-repeat as below
if (scope.$last){
window.alert("im the last!");
}
I have done something like this , so I will post my code here maybe it can help you. First I created custom directive
'use strict';
angular.module('myApp')
.directive('onFinishRender',['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('tilesCreated');
});
}
}
}
}]);
Then used this directive with ng-repeat
<div on-finish-render="" ng-repeat="tile in tiles" ></div>
Then I listen for this event inside another directive
scope.$on('tilesCreated', function() {
// Do something here
});
P.S. Notice that in here I do all things on the same scope. But it depends on you if you want you can broadcast events on $rootScope. I just wanted to give you base idea of how to do this. I hope it will help.

Can't create a popover with custom template

First I tried using angular-ui
<span popover-template="removePopover.html" popover-title="Remove?" class="glyphicon glyphicon-remove cursor-select"></span>
here the template is not included and no errors are provided in console. As I undestood from previous questions this capability is still in development (using v0.13.0).
Then I tried using bootstrap's popover
<span delete-popover row-index={{$index}} data-placement="left" class="glyphicon glyphicon-remove cursor-select"></span>
This is included to popover
<div id="removePopover" style="display: none">
<button id="remove" type="button" ng-click="removeElement()" class="btn btn-danger">Remove</button>
<button type="button" ng-click="cancelElement()" class="btn btn-warning">Cancel</button>
</div>
This is the managing directive
app.directive('deletePopover', function(){
return{
link: function(scope, element, attrs) {
$(element).popover({
html : true,
container : element,
content: function() {
return $('#removePopover').html();
}
});
scope.removeElement = function(){
console.log("remove"); //does not get here
}
scope.cancelElement = function(){
console.log("cancel"); //does not get here
}
}
};
});
In case of bootstrap's popover the scope is messed up. cancelElement() call does not arrive in directive neither the parent controller.
If anyone could help me get atleast on of these working it would be great.

How to access a dom element in a Angular directive

I am trying to attach a keyup event to a directive in my Angular project. Here is the directive:
angular.module('clinicalApp').directive('chatContainer', function() {
return {
scope: {
encounter: '=',
count: '='
}
templateUrl: 'views/chat.container.html',
link: function(scope, elem, attrs) {
scope.count = 500;
}
};
});
And here is the html from the template:
<div class="span4 chat-container">
<div class="chat-body">
<form accept-charset="UTF-8" action="#" method="POST">
<div class="text-area-container">
<textarea id="chatBox" class="chat-box" rows="2"></textarea>
</div>
<div class="button-container btn-group btn-group-chat">
<input id="comment" class="btn btn-primary btn-small btn-comment disabled" value="Comment" ng-click="addMessage()"/>
</div>
</form>
</div>
</div>
</div>
I want to access the chatbox in my link function and attach the keyup event to it. I know I can get it with jQuery, but that cannot be the Angular way. What is the proper way to grab that element from the dom?
You can easily do it with Angular' element' find() method:
var chatbox = elem.find("textarea"); // Finding
chatbox.bind("keyup",function(){ // Binding
console.log("KEYUP!")
})
Live example: http://jsfiddle.net/cherniv/S7XdK/
You can use element.find(yourSelector) as previously mentioned, but it is better to use ngKeyUp, similar to how you would use ngClick:
https://docs.angularjs.org/api/ng/directive/ngKeyup

Resources