Angular directive injecting DOM element with click event - angularjs

Content edited
The goal is to create a directive that can be attached to a textbox that, when the textbox has focus, an image/button will appear after the textbox and the image/button click event will fire a function contained within the directive. The goal is for this functionality to be entirely self-contained in the directive so it can be easily deployed in many pages or apps.
The image/button appears after the textbox with no problem but the click event of the button does not fire the function. I have created a plunkr with the example code.
In the plunk, line 15 defines a function called 'search,' which does nothing more than fire an alert. When the textbox has focus, the button appears as expected and line 34 calls the search function successfully, which means the function itself is working. However, the button's click event doesn't fire the search function.
Original post
I'm trying to recreate some functionality in our apps that is currently being accomplished with jQuery. The functionality involves attaching a pseudo-class to a textbox which is then picked up by jQuery and an image of a magnifying glass is injected into the DOM immediately after the textbox. Clicking on the image causes a dialog box to pop open.
What I've accomplished so far is a simple html page, a simple controller, and a simple directive. When the textbox has focus, the image appears as expected. However, the ng-click directive does not fire.
Here's the html:
<input
id="txtAlias"
type="text"
ng-model="pc.results"
user-search />
</div>
Here is the controller:
angular
.module('app')
.controller('PeopleController', PeopleController);
PeopleController.$inject = ['$http'];
function PeopleController() {
var pc = this;
pc.results = '';
pc.search = function () {
alert('test');
};
}
And this is the directive:
angular
.module('app')
.directive('userSearch', userSearch);
function userSearch($compile) {
return {
restrict: 'EAC',
require: 'ngModel',
//transclude: true,
scope: {
//search : function(callerid){
// alert(callerid);
//}
},
template: "The user's alias is: <b><span ng-bind='pc.results'></span>.",
//controller: UserSearchController,
link: function (scope, element, attrs) {
element.bind('focus', function () {
//alert(attrs.id + ' || ' + attrs.userSearch);
var nextElement = element.parent().find('.openuserdialog').length;
if (nextElement == 0) {
var magnifyingglass = $compile('<img src="' + homePath + 'Images/zoomHS.png" ' +
'alt="User Search" ' +
'ng-click="pc.search("' + attrs.id + '")" ' +
'class="openuserdialog">')(scope);
element.after(magnifyingglass);
}
});
}
};
};
For the time being, I'd be happy to get an alert to fire by either hitting pc.search in the controller or by search in the isolated scope. So far, neither has worked. I'm sure it's something simple that's missing but I can't figure out what.
Solution
Thanks to a user over at the Google forum for showing me the controllerAs property for directives. This version now works perfectly:
angular
.module('app')
.directive('userSearch', userSearch);
function userSearch($compile){
return {
controller: function ()
{
this.search = function () {
alert('Test');
};
},
link: function (scope, element, attrs) {
element.bind('focus', function () {
var nextElement = element.parent().find('.openuserdialog').length;
if (nextElement === 0) {
var btn = '<img src="' + homePath + 'Images/zoomHS.png" ' +
'ng-click="userSearch.search()" ' +
'class="openuserdialog" />';
element.after($compile(btn)(scope));
}
});
},
controllerAs: 'userSearch'
};
};

You are using isolated scope in your directive which means it don't have access to its parent scope. So in this case you need to pass your method reference explicitly to directive. Passed method reference to your directive scope by new variable inside a isolated scope of directive.
Markup
<input id="txtAlias"
type="text" ng-model="pc.results"
user-search search="search(id)" />
scope: {
search: '&'
}
As you don't have access to parent scope, you can't use controller alias over there like you are using pc.. Simply do use following without alias. So directive will bind those variables from directive scope directly.
template: "The user's alias is: <b><span ng-bind='results'></span>.",
Also change compile template to
if (nextElement == 0) {
var magnifyingglass = $compile('<img src="' + homePath + 'Images/zoomHS.png" ' +
'alt="User Search" ' +
'ng-click="search({id: ' + attrs.id + '})' +
'class="openuserdialog">')(scope);
element.after(magnifyingglass);
}
Rather I'd love to have the compiled template as part of template of directive function only. And I'll show and hide it based on ng-if="expression" directive.
Relative answer

Rather than trying to inject into the DOM, and then trying to hook up to that thing you just injected, wrap both the input and the search button/icon in a directive. You can use an isolated scope and two-way binding to hook up both the input and the button:
HTML:
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<search-box data="name" search-function="search"></search-box>
</body>
Here's both a controller and a directive that demonstrate this. Note the "=" in the isolated scope, creating a two-way binding to the corresponding attributes when the directive is used in a template.
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.search= function() { alert('search clicked'); }
});
app.directive('searchBox', function() {
return {
restrict: 'E',
scope: {
searchFunction: '=',
data: '=',
},
template: '<input ng-model="data" /><button ng-click="searchFunction()">Search</button>'
}
})
You should be able to easily replace the button element with an img or whatever else your heart desires.
Here's a plunk with an alert() for the search box, and where typing in the text box in the directive affects the corresponding property of the controller scope:
http://plnkr.co/edit/0lj4AmjOgwNZ2DJMSHDj

Related

Angular 1.2: custom directive's dynamic content: ng-click doesn't work

I am trying to do a very simple thing in Angular 1.2: I want to create dynamic content for my custom directive, and add a click handler (clickCustomer) to parts of it. However, when I do that in the following pattern, whilst the clickCustomer function is available on the element's scope, it is not invoked when clicking on it. I'm gussing I need to get Angular to compile the dynamic content, but I'm not sure if that's actually the case, and if it is, how to do so.
'use strict';
angular.module('directives.customers')
.directive('customers', function () {
return {
restrict: 'A',
replace: true,
template: '<div class="customers"></div>',
controller: function ($scope, $element) {
var customers = ['Customer1', 'Customer2', 'Customer3'];
var customersMapped = customers.map(function (customer) {
return '<span ng-click="clickCustomer()" data-customer="' + customer + '">' + customer + '</span>';
});
var text = customersMapped.join(', ');
$element.html(text);
$scope.clickCustomer = function (event) {
console.log('Customer clicked', event);
}
}
};
});
You're right, you need to use the $compile service and compile the attached DOM elements so Angular will set up the events and scopes.
Check this fiddle.

How to get modified values from controller to custom directive?

I have defined one variable in controller and i have assigned this value to one attribute of custom directive. So on the basis of this value i am showing the modal box template. Now if i click on the cancel button from modal box template then it calls one function from controller which is modifying the variable value to false but it is not hiding the popup box. Please help me to fix it.
(function () {
'use strict';
angular.module('module1').directive('myDirective', function () {
function linkFunction(scope, elem, attrs) {
//scope.openvalue = attrs.openvalue;
scope.closevalue = false;
scope.close = function () {
console.log("Inside Close");
scope.openvalue = false;
scope.closevalue = false;
};
};
return {
templateUrl: 'confirmTemplate.html',
restrict: 'E',
link: linkFunction,
scope: {
confirmtext: '#',
openvalue: '=',
closeconfirm: '&',
submitconfirm: '&'
},
controller: ['$scope', function ($scope) {
$scope.$watch('openvalue', function () {
console.log("OpenValue : " + $scope.openvalue);
});
}]
};
});
})();
Following is the html for opening this modal.
<div class="col-xs-12 options" ng-click="cntrl.flag1 = true">
<div class="row">
<myDirective openvalue="cntrl.flag1" confirmtext="This is the text from directive"
closeconfirm="cntrl.closeconfirm()" submitconfirm="cntrl.submitconfirm()"></myDirective>
<div class="col-xs-9 no-left-right-padding">My text</div>
</div>
</div>
And i want the updated value of openvalue inside html template but it is not working.
It would be more clear to have you codes here, but I think the problem is when you call the function from controller, it doesn't actually modify the variable of controller scope but modal's scope.
In AngularJS scope, any change of inherited variable in child scope will create a local version.
Based on your words, when you open a modal window it will create a new child scope and when you call the function from controller to modify that scope variable, you actually modifying that child scope variable not controller's.
You can simply add console.log($scope.$id); in controller and the function then you should be able to see the scope id is different.
This Fiddle will give you the idea, press Esc key to close the modal window. However, as I said it would be better to have your code to address exact issue.
Based on your code, a quick fix is assign the cntrl object into directive which will make sure your directive refer to the same object.
Change your modal to
<myDirective cntrl="cntrl" confirmtext="This is the text from directive"></myDirective>
in your directive
scope: {
confirmtext : '#',
cntrl : '='
},
in your linkFunction
function linkFunction(scope, elem, attrs){
scope.close = function(){
scope.cntrl.flag1 = false;
}
you still can access closeconfirm and submitconfirm by $scope.cntrl.closeconfirm and $scope.cntrl.submitconfirm respectively.

Controller must wait until Directive is loaded

I'm currently writing a small set of AngularJS controls, and I'm encountering an issue here.
The control which I'm creating is a button (let's start simple).
The directive looks like this:
officeButton.directive('officeButton', function() {
return {
restrict: 'E',
replace: false,
scope: {
isDefault: '#',
isDisabled: '#',
control: '=',
label: '#'
},
template: '<div class="button-wrapper" data-ng-click="onClick()">' +
'<a href="#" class="button normal-button">' +
'<span>{{label}}</span>' +
'</a>' +
'</div>',
controller: ['$scope', function($scope) {
// Controller code removed for clarity.
}],
link: function(scope, element, attributes, controller) {
// Link code removed for clarity.
}
}
});
I've removed some code because it will make my question very hard to understand and I don't believe it's needed here.
Inside my controller of my directive, I'm writing an API and that being done by executing the following code:
controller: ['$scope', function($scope) {
// Allows an API on the directive.
$scope.api = $scope.control || {};
$scope.api.changeLabel = function(label)
$scope.label = label;
}
}]
So, on my directive, I do have an isolated scope to which I can pass a control property. Through this property, I'll have access to the method changeLabel.
My control can be rendered by using the following directive in the HTML website:
<office-button control="buttonController"></office-button>
The controller on my application will looks like:
officeWebControlsApplication.controller('OfficeWebControlsController', ['$scope', function($scope) {
$scope.buttonController = {};
}]);
I can execute my changeLabel method right now by calling the following method in my scope
$scope.buttonController.changeLabel('Demo');
However, this doesn't work since at this point changeLabel is not defined. The button must first be completely rendered.
Any idea on how to resolve this particular issue?
Edit: Plunker Added
I've added a plunker to provide more information.
When the changeLabel method is called within the onClick event, then everything is working, but if I call it outside, it isn't working anymore. This is because the $scope.api.changeLabel is not defined at the moment of execution (button is not rendered yet). So I basically have to wait in my application's controller until the button is fully rendered.
Kind regards

Binding data in a custom directive - AngularJS

I have a custom directive, and its purpose is to present a widget and bind it to a variable.
Every variable has different data type, so different widgets will be presented depending on the data type.
My problem is that I can pass the data of the variable, but I can't manage to bind the widget to it.
To simplify the problem, my widget is just a simple text input.
When I try to $compile the widget, Angular uses the value of the variable instead of binding to it.
HTML:
<body ng-app="app" ng-controller="myCtrl">
<input type="text" ng-model="resource.name"></div>
<div custom-widget widget-type="widget" bind-to="resource"></div>
</body>
Javascript:
angular.module('app', [])
.directive('customWidget', function($compile) {
return {
replace: true,
template: '<div></div>',
controller: function($scope) {
},
scope: {
bindTo: "=bindTo",
widgetType: "=widgetType"
},
link: function(scope, iElem, iAttrs) {
var html = '<div>' + scope.widgetType.label + ':<input ng-bind="' + scope.bindTo[scope.widgetType.id] + '" /></div>';
iElem.replaceWith($compile(html)(scope));
}
};
})
.controller('myCtrl', function($scope) {
$scope.widget = {
id: 'name',
label: 'Text input',
type: 'text'
};
$scope.resource = {
name: 'John'
};
});
Plunker demo: http://plnkr.co/edit/qhUdNhjSN7NlP4xRVcEA?p=preview
I'm still new to AngularJS and my approach may not be the best, so any different ideas are of course appreciated!
Since you're using an isolate scope one issue is that resource is on the parents scope and not visible within the directive. And I think you're looking for ng-model rather than ng-bind.
Also, since you want to bind to namein resource, we need to tie that in somehow.
So here's one approach to your template html (note the addition of $parent to get around the scope issue and the addition of .name(which you could add programatically using a variable if you preferred, or specify it as part of the attribute))
var html = '<div>' + scope.widgetType.label + ':<input ng-model="' + '$parent.' + iAttrs.bindTo +'.name'+ '" /></div>';
Updated plunker
Well, when you have a isolated scope within your directive and use the "=" operator you already have two-way data binding.
My suggestion would be to use the "template" more like a view so the operations are clearer.
I would change your directive to the following:
Using ng-model instead of ng-bing mainly because as the Documentation reveals:
The ngModel directive binds an input,select, textarea (or custom form control) to a property on the scope using NgModelController, which is created and exposed by this directive. [...]
Changed directive:
angular.module('app', [])
.directive('customWidget', function($compile) {
return {
replace: true,
template: '<div> {{widgetType.label}} <input ng-model="bindTo[widgetType.id]" /></div>',
scope: {
bindTo: "=bindTo",
widgetType: "=widgetType"
}
};
});
EDIT:
Ops forgot the Updated Plunker

How to share data exclusively between a directive and a factory in AngularJS?

I want to build a modal directive with an Async API to open the modal.
What I was thinking about is a factory that returns me the API object that manipulates the modal directive. Using Angular-UI Bootstrap 0.6.0 I wanted something like so:
Module.factory("ModalAPI", function ($modal)
var ModalAPI;
ModalAPI.confirm = function (title, text, buttons) {
// Set up a confirmation modal
return $modal.open(options).result;
};
return ModalAPI;
});
So far so good. Now to set up the modal I would something like:
Module.factory("ModalAPI", function ($modal)
var ModalAPI,
Modal;
Modal = {};
ModalAPI.contents = function () {
return Modal;
}
ModalAPI.confirm = function (title, text, buttons) {
Modal.title = title;
Modal.text = text;
Modal.buttons = processButtons(buttons);
return $modal.open(options).result;
};
return ModalAPI;
});
Module.directive("modal", function(ModalAPI) {
return {
restrict: "A",
link: function (scope, elem, attrs) {
scope.modal = ModalAPI.contents();
},
template: "<div class='modal-header'>" +
" <h2>modal.title</h2>" +
"</div>" +
"<div class='modal-body'>" +
" <h2>modal.text</h2>" +
"</div>" +
"<div class='modal-footer'>" +
" <button ng-click='$close(button.result)' ng-repeat='button in buttons'>{{button.label}}</button>" +
"</div>"
};
});
The problem is how to set up the modal sharing the Modal object only with the directive and no one else?
The directive definition object allows you to define a controller for the directive. From the documentation:
controller - Controller constructor function. The controller is
instantiated before the pre-linking phase and it is shared with other
directives (see require attribute). This allows the directives to
communicate with each other and augment each other's behavior. The
controller is injectable (and supports bracket notation) with the
following locals:
$scope - Current scope associated with the element
$element - Current element
$attrs - Current attributes object for the element
$transclude- A transclude linking function pre-bound to the correct transclusion scope: function(cloneLinkingFn).
The controller on a directive behaves just like a controller for an ng-view. I think it's just what you are looking for.

Resources