2
votes

I'm having trouble with custom directives with <input ...> tags in it's template, when they are inside an angular form.

If you declare an input inside a form, editing the input's field will alter form's properties, like pristine, touched, valid, etc, as expected.

If you declare a custom directive inside a form, say <ac2-string-input ...></ac2-string-input> and it's template includes an input, then if you edit this input's field, it WILL NOT alter the form's property.

Why is that? Is that a bug? Is there any workaround for this?

Below you have an example:

We can have a form component, in app/form.component.ts

import { Component } from '@angular/core'

import { InputComponent } from './input.component'

@Component({
    selector: 'ac2-form',
    templateUrl: '<build path>/templates/form/form.html',
    directives: [InputComponent]
})

export class FormComponent {


    item: Object = {
       attr1: 'blah',
       attr2: 'another blah'
    }

    constructor(){}

    submit(){ console.log(this.item) }
}

with a template templates/form/form.html

<form #f="ngForm" (ngSubmit)="submit()">
  <input type="text" name="attr1" [(ngModel)]="item.attr1"/>
  <ac2-string-input [obj]="item"></ac2-string-input>

  <button type="submit">Submit</button>
</form>

<div>
    Pristine? {{f.form.pristine}}
</div>

And ac2-string-input directive is defined in app/form/input.component.ts

import { Component, Input } from '@angular/core'

@Component({
    selector: 'ac2-string-input',
    templateUrl: '<build path>/templates/form/input.html'
})

export class InputComponent {
    @Input() obj: Object;

    constructor(){}
}

with a template templates/form/input.html

<input type="text" name="attr2" [(ngModel)]="obj.attr2"/>

If we load the form, there will be two text fields, and the form will be "Pristine"

Form

If we edit the "attr2" field, the form will continue to be pristine, as if the field was not bound to the form!

field edited, form pristine

If we edit the "attr1" field, the form will not be pristine, as expected.

2

2 Answers

3
votes

I've opened an issue in Angular 2's github.

Turns out that if we want our component to be recognized as a form control, we need to implement ControlValueAccessor interface and put ngModel at the top level.

THIS Plunkr shows how to do this.

Credits go to kara who solved this issue for me.

0
votes

The inner input is probably not recognized as being part of the form, since ac2-string-input is not a standard HTML input.

You can verify this by outputting the forms' controls or value and looking for the attr2 property. If it's not there, Angular doesn't even know about it, so changing that input has no impact on the form's pristine state.

To make integration easier, consider using an attribute directive instead of a component. This would change:

<ac2-string-input [obj]="item"></ac2-string-input>

To

<input type="text" [ac2StringInput]="item"/>

And the Directive would be something like:

@Directive({
    selector: '[ac2StringInput]',
    host: {
        // onKeyup gets executed whenever the input receives input
        '(keyup)':'onKeyup($event)'
    }
})
export class InputComponent {
    constructor() {}

    /**
     * ac2StringInput to the outside refers to obj within this directive
     */
    @Input('ac2StringInput') obj:Object;

    /**Handle keyup*/
    onKeyup($event){}
}

You could even pass the whole form as an input into the directive if you want more visibility into what's going on outside of it.