16
votes

I have a template driven form in Angular 2.1 containing many standard controls (<input>, <select> etc) and a custom control which itself contains multiple input elements.

I've implemented ControlValueAccessor on the custom control and it is propagating it's changed/touched/valid values correctly to the parent form.

However .. on the parent form I have a Save button, on which after saving I want to clear the dirty/touched state (as this affects the css applied) like this:

save(myForm: NgForm) {

    myForm.form.markAsPristine();
    myForm.form.markAsUntouched();

}

This is working ok for all the elements in the top level parent form and the custom control itself but the <input> fields within the custom control are still marked as touched/dirty (and this receiving the pre-saved styling).

Is there a way that the custom control can be notified when it's dirty/touched state is changed so that it can then clear it's child <input> elements to match? It seems that if the <input> elements are within a custom control they don't get updated by calls to markAsPristine/Untouched on the parent Form.

Thanks!

4

4 Answers

13
votes

Try to add controls['nameOfControl'] like this

 myForm.form.controls['nameOfControl'].markAsPristine();

The code above will only work for form controls.

THe following seems to be a good work around:

  active = true;
  newModel() {
    this.model = new yourModel(2, '',true, '');
    this.active = false;
    setTimeout(() => this.active = true, 0);
  }

Reset the form with a new Model AND to restore the 'pristine' class state. by toggling the 'active' flag will cause the form to be removed/re-added in a tick via NgIf. Yes it is a small work around until they can can fix :)

hope that helps

14
votes

The workaround with the active flag does the job but I also found another way.

On the parent form, I can access my custom child component as ViewChild.

i.e in the parent form:

@ViewChild(CustomChildComponent) child1: CustomChildComponent;

Then when I save in the parent form, call that child directly.

save(myForm: NgForm) {

   // clear anything at the parent form level
   myForm.form.markAsPristine();
   myForm.form.markAsUntouched();

   // tell the custom component to do the same
   this.child1.markAsPristineAndUntouched();
}

Where in CustomChildComponent I've defined ..

// get the input controls in the the child that we want to clear 
@ViewChild('surname') surnameField: NgModel;

markAsPristineAndUntouched() { 

   this.surnameField.control.markAsPristine();
   this.surnameField.control.markAsUntouched();
   // .. clear other controls .. 
}
2
votes

markAsPristine() can affect the control itself, and possibly all its direct ancestor controls (i.e. parents, grandparents, great grandparents)

markAsPristine() takes an opts argument; markAsPristine(opts: { onlySelf?: boolean; } = {}). When the onlySelf value is false (which is the default value )), the control and all its direct ancestors are marked as pristine. If onlySelf is true; only the control itself is marked as pristine.

In my case, I wanted to mark all descendant controls (i.e. child, grandchild, etc) as pristine/untouched. I can do this using FormGroup.reset() ; reset() has the ability to affect descendants:

Resets the FormGroup, marks all descendants are marked pristine and untouched, and the value of all descendants to null.

So with the OP's code I could reset the form, using the values it already has:

myForm.form.reset(myForm.form.getRawValue(), {emitEvent: false})

Note how I avoid certain side effects:

  • I use the form's current values, so I don't change current values.
  • I use getRawValue(), instead of value, to get the value of disabled controls.
  • I don't emitEvent; which means no valueChanges will be triggered.
0
votes

My solution is to observe the host changes.

I have a component implementing the controlValueAccesor interface. I use this component into a form, with ngModel.

When Angular forms api changes the host element class attribute setting it to dirty, prinstine, touched, etc... I set mi component class attibute to the host class attribute.

constructor(private hostElement: ElementRef) {

this.changes = new MutationObserver((mutations: MutationRecord[]) => {
  mutations.forEach(
    (mutation: MutationRecord) => (this.controlClasses = mutation.target['className']),
  );
});

this.changes.observe(this.hostElement.nativeElement, {
  attributes: true,
  attributeFilter: ['class'],
});

}

On template

 <span class="ui-float-label">
  <input
    #inputElement
    [disabled]="disabled"
    [type]="type"
    [required]="required"
    [(ngModel)]="value"
    (ngModelChange)="handleValueChange($event)"
    (blur)="onBlur()"
    [pattern]="pattern"
    [type]="type"
    [size]="size"
    pInputText
    class="p-inputtext"
    [ngClass]="controlClasses"
  />

  <label *ngIf="!required" for="float-input">{{ placeholder }}</label>
  <label *ngIf="required" for="float-input">{{ placeholder }}*</label>
</span>

Now each time the host change his classes the change is propagated to the child control.