You're creating 2 separate ngModel instances, that are both updated when the input's changed.
The first is created by the <input> itself, which is the one assigned to 'myform'. This is the one that the <span> error message within my-wrapper is bound too.
The second one is the one created by the my-wrapper directive - which is the one that has the validator attached to it.
If you check the console (for the plnkr below) and inspect the values being output by the validator when the input is changed, you can see that the ngModel associated with the validator, is not the same ngModel that's associated with the form. But that both are actually being updated when the input's changed.
Clear the console once the page has loaded and then check the output when you change the first input.
http://plnkr.co/edit/nz6ODOVpn6lJlb055Svs?p=preview
Why is this happening?
Because both ng-model directives get passed the same string ('values.abc'), which are then evaluated against scope to determine which object property they should watch and update - i.e two way binding.
So when you change the input you're changing the value of scope.values.abc through the inputs ngModel instance. This change is picked up by the my-wrapper ngModelinstance - as it's watching the same object property - that then validates itself.
You can't solve the problem in this way, as the ngModel directive expects a string, not another ngModelinstance.
Solution
You could transfer the attributes from my-wrapper to the input at compile:
app.directive("myWrapper", function(){
var templateFn = function(element, attrs){
return '<div ng-form="myform">'+
'<input type="text" name="myfield"/>'+
'<span>(inside directive) : isDuplicate:{{myform.myfield.$error.isDuplicate}}</span>'
'</div>';
}
return {
restrict :'E',
template : templateFn,
require: 'ngModel',
scope: true,
compile: function(element, attrs) {
var attr;
angular.forEach(element.find('input'), function(elem) {
elem = angular.element(elem)
for(attr in attrs.$attr) {
elem.attr(attrs.$attr[attr], attrs[attr]);
}
});
for(attr in attrs.$attr) {
element.removeAttr(attrs.$attr[attr]);
}
}
}
});
http://plnkr.co/edit/m2TV4BZKuyHz3JuLjHrY?p=preview