Access parent data model in directive - angularjs

I wrote a directive to check for double values in a data column:
The markup
<table>
<tr data-ng-repeat-start="rowItem in vm.model.data" ...>
<td>
<input type="text" data-ng-model="rowItem.ID" data-unique-column="vm.model.data" />
</td>
...
</tr>
</table>
and the directive
(function () {
'use strict';
angular.module('app').directive('uniqueColumn', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
element.on('keyup blur', function () {
scope.$eval(attrs.uniqueColumn).forEach(function (item) {
// validation logic
});
});
}
};
});
})();
Everything works fine but I asked myself if there was a solution to access the data of my repeater, i.e. vm.model.data, without passing an argument to the directive?

Because you didn't isolate the scope, you can read javascript's prototypal inheritance. You can perhaps access a method from the child that will then lookup to the parent's. And perhaps use the $index as the parameter.

Why not use another directive to define the unique-column's model and add it as a dependency on the unique-column directive, like this:
unique-column-model directive:
app.directive('uniqueColumnModel', function() {
return {
restrict: 'A',
controller: function($scope) {
// add more logic here if necessary
},
link: function (scope, element, attrs, ngModel) {
// save the value of the uniqueColumnModel as a private var in the scope
scope.$uniqueColumnModel = scope.$eval(attrs.uniqueColumnModel);
}
};
});
unique-column directive:
app.directive('uniqueColumn', function() {
return {
restrict: 'A',
// set the uniqueColumnModel directive as a dependency (^ is to search on parents)
require: ['ngModel', '^uniqueColumnModel'],
link: function (scope, element, attrs, ngModel) {
element.on('keyup blur', function () {
// use the private $uniqueColumnModel var that was
// previously saved on the scope
angular.forEach(scope.$uniqueColumnModel, function(item) {
// validation logic
});
});
}
};
});
And on the HTML:
<tr ng-repeat="item in model.items" unique-column-model="model.items">
<td>
<input type="text" ng-model="item.id" unique-column />
</td>
</tr>
Check this plunker.
There's still room for improvement but the idea is there...

Related

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

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);
...}

Having multiple suffix for same directive (angularjs)

Let say we have the following directive:
app.directive('testList', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
//do stuff
}
}
});
I would like to call this directive from multiple element in html
<button test-list-add></button>
<table test-list="listObject">
<tr ng-repeat="list in testList">
<td test-list-click>list.item</td>
</tr>
</table>
is it possible to get into the link function for each attribute starting with test-list (test-list-add, test-list, test-list-click)?
Thank you
You want to pass attributes to your directive and doing some action, depending on the attribute:
app.directive('testList', function () {
return {
restrict: 'A',
scope: {
'add': '#', // Text
'click': '&' // function
},
link: function (scope, element, attrs) {
// scope.add - your Text you passed
// scope.click(); - calls your function you passed
}
}
});
<button test-list add="test"></button>
<table test-list="listObject">
<tr ng-repeat="list in testList">
<td test-list click="myFunc()">list.item</td>
</tr>
</table>
You can't repeat a directive (what you obvis trying to do).
If you want to make multiple suffix for a directive, you have to declare them one by one:
app.directive('testListAdd', function () {...});
app.directive('testListclick', function () {...});
<test-list-add></test-list-add>
<test-listclick></test-listclick>

Directive that returns template needs scope variable

The template that loads content("html/script template") for each cell:
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index" >
<div ng-init="values = dbo.get4(attobj.key); key = attobj.key; template = attobj.template || getAttributeTemplate(dbo.clazz + attobj.key);">
<div plain-template="template">
</div>
</div>
</td>
I need the directive to access the value "template" from its directive "plain-template"
Directive:
app.directive('plainTemplate', function($parse) {
return {
templateUrl: 'testTemplate',
function (scope, element, attrs) {
console.log($parse(attrs.plainTemplate));
}
};
});
You can use something like
app.directive('plainTemplate', function($parse) {
return {
templateUrl: 'testTemplate',
scope: {
plainTemplate:"="
},
link: function (scope, element, attrs) {
console.log(scope.plainTemplate));
}
};
});
and when you call it in html your variable should be like
<plain-template test-template="anything"> </plain-template>
this is isolated scope. You can see more at https://docs.angularjs.org/guide/directive

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!

Two way data binding failing with ng-model

My two-way data binding using ng-model is not working.
AngularJS Docs
Relevant Question
Parent directive template:
<div class="form-group">
<label>Company Phone</label>
<input ng-model="formData.company_phone" type="phonenumber" class="form-control" placeholder="Company Phone">
</div>
And the child directive:
.directive('input', [function(){
return: {
restrict: 'E',
require: '?ngModel',//right now this is binding to this directive scope, not a parent one
link: function($scope, element, attr, ngModel){
if (attr.type !== 'phonenumber') {
return;
}
//some code to validate a phone number
$scope.$apply(function () {
//bind updated number, but I need this to reflect in the parent scope
$scope[attr.ngModel] = formattedNumber;
}
I used $parse to solve this issue:
.directive('input', ['$parse', function($parse){
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, element, attr, ngModel){
var getter = $parse(attr.ngModel);
var setter = getter.assign;
if (attr.type !== 'phonenumber') {
return;
}
//code that validated a phone number
scope.$apply(function () {
setter(scope, formattedNumber);
});
}
}
}]);

Resources