diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart
index 51628aaa7..8852c603c 100644
--- a/lib/directive/ng_control.dart
+++ b/lib/directive/ng_control.dart
@@ -5,6 +5,8 @@ abstract class NgControl implements NgDetachAware {
   static const NG_INVALID_CLASS        = "ng-invalid";
   static const NG_PRISTINE_CLASS       = "ng-pristine";
   static const NG_DIRTY_CLASS          = "ng-dirty";
+  static const NG_TOUCHED_CLASS        = "ng-touched";
+  static const NG_UNTOUCHED_CLASS      = "ng-untouched";
   static const NG_SUBMIT_VALID_CLASS   = "ng-submit-valid";
   static const NG_SUBMIT_INVALID_CLASS = "ng-submit-invalid";
 
@@ -13,6 +15,8 @@ abstract class NgControl implements NgDetachAware {
   bool _pristine;
   bool _valid;
   bool _invalid;
+  bool _touched;
+  bool _untouched;
 
   final Scope _scope;
   final NgControl _parentControl;
@@ -26,6 +30,8 @@ abstract class NgControl implements NgDetachAware {
       : _parentControl = injector.parent.get(NgControl)
   {
     pristine = true;
+    untouched = true;
+
     _scope.on('submitNgControl').listen((e) => _onSubmit(e.data));
   }
 
@@ -37,6 +43,7 @@ abstract class NgControl implements NgDetachAware {
 
   reset() {
     _scope.broadcast('resetNgModel');
+    untouched = true;
   }
 
   _onSubmit(bool valid) {
@@ -91,6 +98,25 @@ abstract class NgControl implements NgDetachAware {
     element.classes..remove(NG_VALID_CLASS)..add(NG_INVALID_CLASS);
   }
 
+  get touched => _touched;
+  set touched(value) {
+    _touched = true;
+    _untouched = false;
+
+    element.classes..remove(NG_UNTOUCHED_CLASS)..add(NG_TOUCHED_CLASS);
+
+    //as soon as one of the controls/models is touched
+    //then all of the parent controls are touched as well
+    _parentControl.touched = true;
+  }
+
+  get untouched => _untouched;
+  set untouched(value) {
+    _touched = false;
+    _untouched = true;
+    element.classes..remove(NG_TOUCHED_CLASS)..add(NG_UNTOUCHED_CLASS);
+  }
+
   /**
    * Registers a form control into the form for validation.
    *
@@ -158,7 +184,7 @@ abstract class NgControl implements NgDetachAware {
 }
 
 class NgNullControl implements NgControl {
-  var _name, _dirty, _valid, _invalid, _pristine, _element;
+  var _name, _dirty, _valid, _invalid, _pristine, _element, _touched, _untouched;
   var _controls, _scope, _parentControl, _controlName;
   var errors, _controlByName;
   dom.Element element;
@@ -185,6 +211,12 @@ class NgNullControl implements NgControl {
   get invalid => null;
   set invalid(value) {}
 
+  get touched => null;
+  set touched(value) {}
+
+  get untouched => null;
+  set untouched(value) {}
+
   reset() => null;
   detach() => null;
 
diff --git a/lib/directive/ng_form.dart b/lib/directive/ng_form.dart
index 2712be93f..c2c4427f7 100644
--- a/lib/directive/ng_form.dart
+++ b/lib/directive/ng_form.dart
@@ -39,6 +39,7 @@ class NgForm extends NgControl implements Map<String, NgControl> {
       element.onSubmit.listen((event) {
         event.preventDefault();
         _scope.broadcast('submitNgControl', valid == null ? false : valid);
+        reset();
       });
     }
   }
diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart
index b4dc3140b..42388645d 100644
--- a/lib/directive/ng_model.dart
+++ b/lib/directive/ng_model.dart
@@ -45,6 +45,7 @@ class NgModel extends NgControl implements NgAttachAware {
   }
 
   reset() {
+    untouched = true;
     modelValue = _lastValue;
   }
 
@@ -203,7 +204,12 @@ class InputTextLikeDirective {
     };
     inputElement
         ..onChange.listen(processValue)
-        ..onInput.listen(processValue);
+        ..onInput.listen(processValue)
+        ..onBlur.listen((e) {
+          if(ngModel.touched == null || ngModel.touched == false) {
+            ngModel.touched = true;
+          }
+        });
   }
 
   processValue([_]) {
diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart
index 3e16d962f..230a6758c 100644
--- a/test/directive/ng_form_spec.dart
+++ b/test/directive/ng_form_spec.dart
@@ -362,6 +362,46 @@ describe('form', () {
       expect(model.modelValue).toEqual('animal');
       expect(model.viewValue).toEqual('animal');
     }));
+
+    it('should set the form control to be untouched when the model is reset or submitted', inject((TestBed _) {
+      var form = _.compile('<form name="duperForm">' + 
+                           ' <input type="text" ng-model="myModel" probe="i" />' +
+                           '</form>');
+      var model = _.rootScope.context['i'].directive(NgModel);
+      var input = model.element;
+
+      NgForm formModel = _.rootScope.context['duperForm'];
+
+      expect(formModel.touched).toBe(false);
+      expect(formModel.untouched).toBe(true);
+      expect(form.classes.contains('ng-touched')).toBe(false);
+      expect(form.classes.contains('ng-untouched')).toBe(true);
+
+      _.triggerEvent(input, 'blur');
+
+      expect(formModel.touched).toBe(true);
+      expect(formModel.untouched).toBe(false);
+      expect(form.classes.contains('ng-touched')).toBe(true);
+      expect(form.classes.contains('ng-untouched')).toBe(false);
+
+      formModel.reset();
+
+      expect(formModel.touched).toBe(false);
+      expect(formModel.untouched).toBe(true);
+      expect(form.classes.contains('ng-touched')).toBe(false);
+      expect(form.classes.contains('ng-untouched')).toBe(true);
+
+      _.triggerEvent(input, 'blur');
+
+      expect(formModel.touched).toBe(true);
+
+      _.triggerEvent(form, 'submit');
+
+      expect(formModel.touched).toBe(false);
+      expect(formModel.untouched).toBe(true);
+      expect(form.classes.contains('ng-touched')).toBe(false);
+      expect(form.classes.contains('ng-untouched')).toBe(true);
+    }));
   });
 
   describe('regression tests: form', () {
diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart
index f3634151e..80c8179ec 100644
--- a/test/directive/ng_model_spec.dart
+++ b/test/directive/ng_model_spec.dart
@@ -707,8 +707,8 @@ describe('ng-model', () {
 
       var model = probe.directive(NgModel);
 
-      var input1 = element.query("#on");
-      var input2 = element.query("#off");
+      var input1 = element.querySelector("#on");
+      var input2 = element.querySelector("#off");
 
       expect(model.pristine).toEqual(true);
       expect(model.dirty).toEqual(false);
@@ -1021,6 +1021,24 @@ describe('ng-model', () {
       expect(model.modelValue).toEqual('animal');
       expect(model.viewValue).toEqual('animal');
     });
+
+    it('should set the model to be untouched when the model is reset', () {
+      var input = _.compile('<input type="text" ng-model="myModel" probe="i" />');
+      var model = _.rootScope.context['i'].directive(NgModel);
+
+      expect(model.touched).toBe(false);
+      expect(model.untouched).toBe(true);
+
+      _.triggerEvent(input, 'blur');
+
+      expect(model.touched).toBe(true);
+      expect(model.untouched).toBe(false);
+
+      model.reset();
+
+      expect(model.touched).toBe(false);
+      expect(model.untouched).toBe(true);
+    });
   });
 });