How to add a button to an input field using AngularJS directive? - angularjs

I need to write a directive that can would add a button to an input field and some other functionality.
So, ideally I want to have something like this
<input type="text" my-directive>
to end up being
<input type="text"><button ng-click="someAction()" ng-class="{'success': isSuccess()}" ng-disabled="isDisabled()">click me</button>
Simplified code for my directive:
app.directive('myDirective', ['$q', '$timeout', function ($q, $timeout) {
return {
restrict: 'A',
template: '<button ng-click="someAction()" ng-class="{\'success\': isSuccess()}" ng-disabled="isDisabled()">click me</button>',
require: '?ngModel',
link: function (scope, element, attrs, ctrl) {
scope.isSuccess = function () {
...
};
scope.isPending = function () {
...
};
scope.someAction = function () {
...
};
....}]);
The problem is that if I add this button in directive's template I end up with <input><button></button></input>
Creating a directive that would include also an input field is unfortunately not an option for me.
Please let me know if I need to provide more info.

After some searching, I have found a solution.
You can add a the element by compiling it in your directive's link function and appending it to the element.
link: function (scope, element, attrs, ctrl) {
var tpl = <button ng-click="someAction()" ng-class="{\'success\': isSuccess()}" ng-disabled="isDisabled()">click me</button>';
var el = $compile(tpl)(scope);
element.after(el);
...}

Related

Proper way to validate an input is numeric in angular

I have the following directive in angular:
define(function(require) {
var angular = require('angular');
angular.module('is-numeric', [])
.directive('isNumeric', [function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elem, attrs, ctrl) {
elem.on('keyup', function(e) {
ctrl.$setValidity('isnumeric', !isNaN(e.target.value));
});
}
};
}]);
});
Do I really need to add an event handler for the keyup or is there a better way?
you should use validators:
link: function(scope, elem, attrs, ctrl) {
ctrl.$validators.isnumeric = function(modelValue, viewValue){
return !isNaN(viewValue);
};
}
For this you should not use any directives.
My suggestion is that use the built in ng-pattern validator, which will handle the case you described.
You can check here: https://docs.angularjs.org/api/ng/directive/ngPattern
<input ng-model="validatedNumber" ng-pattern=$scope.numberValidation/>
and in you controller you should define the regular expression like this:
$scope.numberValidation = /^(0|[1-9][0-9]*)$/;
Edit: what I forgot in my explanation is that, you should put this into a form, then when you would like to validate/send the form you should check the form validity like this:
if(formname.$valid())
where formname is the name of the DOM element which is defined as a form
<form name="formname"> [...] </form>

Find scope of clicked element in ng-repeat

is it possible to get the clicked row (scope) of a element in ng-repeat without nowing the name?
For example: scope.person works, but when I change persons into another name I can't use my directive global.
I need the data to do a edit like this: http://vitalets.github.io/x-editable/
I will write a directive which changes the text into a input field. To save it I need the name of the field, "lastname" in the example and the ID from the row.
HTML:
<tr ng-repeat="person in persons | orderBy:lastname">
<td class="editable">{{person.lastname}}</td>
<td>{{person.surname}}</td>
</tr>
Directive:
app.directive('editable', [function () {
return {
restrict: 'C',
link: function (scope, elem, attrs) {
elem.bind('click', function(event) {
console.log(scope.person)
});
}
};
}])
You can use Angular's tracking ng-repeats by $index and pass it to your directive:
HTML:
<tr ng-repeat="person in persons | orderBy:lastname">
<td editable="$index" class="editable">{{person.lastname}}</td>
<td>{{person.surname}}</td>
</tr>
Directive:
app.directive('editable', [function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
elem.bind('click', function(event) {
console.log(attrs.Editable);
});
}
};
}])
You are already catching the current scope and it is working fine you just need to manipulate the data and apply scope.$apply();
app.directive('editable', [function () {
return {
restrict: 'C',
link: function (scope, elem, attrs) {
elem.bind('click', function(event) {
console.log(scope.person)
scope.person.surname="hello"
console.log(scope.person)
scope.$apply();
});
}
};
}])
In the first console output you can see the original clicked element and in second console you can see the maupilated data.
Now why apply is required:- It is required to run the digest cycle of angular to adapt the change happen outside the scope of angular js (bind is jquery event not angular's).
Plunker
Add a Scope to your directive so you can pass any Model to be edited.
And display the ID with the Attrs variable.
app.directive('editable', [function () {
return {
scope: {
model : '='
},
restrict: 'C',
link: function (scope, elem, attrs) {
elem.bind('click', function(event) {
alert(scope.model +" "+ attrs.id )
});
}
};}]);
the html:
<span class="editable" model="person.lastname" id="{{$index}}">{{person.lastname}}</span>
The Fiddle
Browse how works the custom directives in angular. You can pass like parameter the lastname through 'attrs' in your link. Something like this:
html code:
<my-first-directive lastname="{{person.lastname}}"></my-first-directive>
custom directive code:
link: function (scope, elem, attrs) {
elem.bind('click', function(event) {
console.log(attrs['lastname'])
});
}
This approach not is good in my opinion. But i hope it works you.
If you read about scopes in directives you will can try other approachs.
You can pass the same parameter to directive scope so.
In your directive:
return {
restrict: 'C',
scope: {
lastname: '=' // If you want know more about this, read about scopes in directives
}
link: function (scope, elem, attrs) {
elem.bind('click', function(event) {
console.log(scope.lastname) // now, scope.lastname is in your directive scope
});
}
};
Be careful whit names when you work with directives. If your variable is 'myVar', when you write the directive html code you need write:
<my-first-directive my-var="myVar"></my-first-directive>
For the same reason that you need write 'my-first-directive' when you name directive is 'myFirstDirective'.
Good luck!

Directives doesn't translate attributes after element replace with jQuery

I tried to do directive, which include template on the fly, but there is one problem - after compile all attributes, including ng-model, aren't translate to new element and ng-model doesn't working.
Where I'm wrong?
Element code:
<input type-test="kendo">
Directive:
App.directive('typeTest', ['$templateCache', '$compile', '$http', 'Formatter',
function ($templateCache, $compile, $http, Formatter) {
return {
restrict: 'A',
scope: {
ngModel: '='
},
replace: true,
link: function(scope, element, attrs) {
$http.get(Formatter.getTemplateUrl(attrs.typeTest), {cache: $templateCache}).success(function(tplContent) {
var el = $compile(tplContent)(scope);
element.replaceWith(el);
});
}
}
}
]);
Formatter.getTemplateUrl() returns a url to template depend on input argument (attrs.typeTest).
Template to type-test="kendo":
<input type="text" kendo-drop-down-list k-data-source="list" k-data-text-field="'Name'" k-data-value-field="'Id'">
List is defined like [{Id: 1, Name: 'First'}, {Id: 2, Name: 'Second'}].
You shouldn't replace the element inside a linking function of a directive. The linking function should just set up event listeners to make the directive work. Place your logic inside the compile function instead of the link function. Here's a pretty good article about it: http://amitgharat.wordpress.com/2013/06/08/the-hitchhikers-guide-to-the-directive/
I find a solution:
App.directive('dynamicType', ['$compile',
function ($compile) {
return {
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
var tpl = "<input type-test='"+iAttrs.dynamicType+"'>";
iElement.html(tpl);
$compile(iElement.contents())(scope);
},
post: function postLink(scope, iElement, iAttrs, controller) {}
}
}
}
}
]);
This directive compile new element, then link it and after return control to typeTest directive - to compile and link other element.
Element:
<input dynamic-type="kendo">

Angular.js trigger another directive

I'm trying to create a sort of generic toggle feature between directives, where one directive which contains a template does not render, until an event occurs from another directive. Any suggestions on how to link this together?
Thanks!
There are many ways to achieve this.
A
Using events (but be careful, when used exessively, especially for interaction between directives, you can get lost easily! This is why I didnt create a http://plnkr.co for it, even worse: code A is untested!
so pls edit this in case of errors
Use $rootScope.$on('myEvent', function(e, eargs) {...}) on the
master directive.
dispatch the event from some directive:
$rootScope.$broadcast('myEvent', {foo: 'bar'}).
remember to inject $rootScope in both directives.
angular.module('masterDirective', [])
.directive('masterDirective', function ($rootScope, $compile /**injects here*/) {
var templ = '<p ng-bind="someVar"></p>';
return {
restrict: 'EA',
scope: {},
link: function (scope, element, attrs) {
scope.someVar = "I am a template and I was born and visible to the world, because slaveDirective send me an event to do so.";
$rootScope.$on('myEvent', function(e, eArgs) {
// eArgs.myVar will be 'Jackson';
element.append($compile(templ)(scope));
});
}
}
});
angular.module('slaveDirective', [])
.directive('slaveDirective', function ($rootScope) {
return {
restrict: 'EA',
scope: {},
link: function (scope, element, attrs) {
$rootScope.$broadcast('myEvent', {myArg: 'Jackson'});
}
}
});
B
Using a "shared controller" is the cleaner, but more complicated way. This approach is more strongly typed, you express the workflow and once it works, it is not as easy to break.
Demo: http://plnkr.co/WaqKzP
Use a controller on your master directive: controller(scope,element,attrs) {...}
require your masterDirective in slave directive: require: 'myMasterDirective'
the controller of the master directive is the fourth parameter of your slave's link function (because you required it), you can call a function to let the master include the template.
<body ng-app="myApp">
<button ng-click="includeSlave=true">include slave directive</button>
<master-directive>
<div ng-if="includeSlave==true">
<slave-directive></slave-directive>
</div>
</master-directive>
</body>
angular.module('myApp', [])
.directive('masterDirective', function ($rootScope, $compile /**injects here*/) {
var templ = '<p ng-bind="someVar"></p>';
return {
restrict: 'E',
controller: function ($scope, $element) {
return {
slaveLink: function() {
$element.append($compile(templ)($scope));
}
}
},
link: function (scope, element, attrs) {
scope.someVar = "I am a template and I was born and visible to the world, because slaveDirective called a function on myself to do so.";
}
};
})
.directive('slaveDirective', function () {
return {
require: '^masterDirective',
restrict: 'E',
link: function (scope, element, attrs, myMasterController) {
myMasterController.slaveLink();
}
};
});

How can I pass multiple params into a directive?

I have a directive:
app.directive('testDir', [function () {
return {
require: '?ngModel',
link: function ($scope, elm, attr, ngModel) {
var abc=attr.testDir;
var def=< The value zzz >
}
};
}])
I understand I can call this like:
<div test-dir='abcd'>xx</div>
What if I also needed to pass the parameter 'zzz'. How can I pass more than one parameter to my directive?
Use multiple attributes. Your directive has access to all attributes used in the element:
<div my-directive arg-one='abcd', arg-two>xx</div>
app.directive('myDirective', function () {
return {
link: function ($scope, elm, attr, ngModel) {
var abc=attr.argOne;
var def=attr.argTwo;
}
};
});
Notice the change from - to camelCase. This is done by AngularJS.
You can use an array like so
<div test-dir='["abcd","zzz"]'>xx</div>
Then in your directive you can do
var abc = JSON.parse(attr.testDir); // = Array ["abcd","zzz"]

Resources