krulik krulik - 11 months ago 76
AngularJS Question

Is it possible to detect changes to ng-model in a directive without a deep $watch

I'm writing a directive with custom validation logic to validate an object.


<input type="hidden" name="obj" ng-model="vm.obj" validate-object />


.directive('validateObject', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$validators.validateObject = myValidator;

function myValidator (modelValue, viewValue) {
return validateObject(modelValue);

function validateObject (obj) {
// Look inside the object

The problem is that the validator doesn't run when a property inside the object is changed.

I could add a
objectEquality === true
, and then manually
with my validation logic. Something like this:

link: function (scope, element, attrs, ngModelCtrl) {
scope.$watch(attrs.ngModel, onModelChange, true);

function onModelChange (newValue) {
ngModelCtrl.$setCustomValidity('validateObject', validateObject(newValue))

function validateObject (obj) {
// Look inside the object

But I don't like using the old school way of manually using
, plus adding a manual
already has ways of registering inside the update process (like
), and in addition the
being a deep one which can has performance issues.

Am I getting this wrong? Is there a better way?

Answer Source

From :

if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {

ngModel performs a flat equality check against the older version of the model, so any changes inside an object would not be reflected on ngModel or ngChange.

The perferred approach would be to use immutable data, that means that every time you change the model (the object), create a new copy instead:

function changeModel(){ = "roy";
  // Create a new object for ngModel;
  this.vm = angular.copy(this.vm);


I remember that I solved a previous issue before. You want to have a set of ng-models binded to properties on an object, and have 1 change listener for the entire object.

Here's my solution:

What I did was to create a new directive "formModel" that must be placed on a form element. Angular has a form directive which has a controller. NgModelController requires a parent form controller, which then it adds itself to the form (this is how you get validity on an entire form). So in my directive, I decorated the form's $addControl method, and added a listener for every ngModelController that adds itself via $viewChangeListeners, and now on every change of ngModel inside the form, the formModel directive will duplicate the entire object and fire $setViewValue.