Serginho
Serginho

Reputation: 7490

Angular 2 advanced transclusion

I have this html code that's working properly:

<div class="form-group"
     [ngClass]="{'has-error': !control.valid && control.dirty, 'has-success': control.valid && control.dirty}">
    <label class="col-sm-3 control-label">User:</label>
    <div class="col-sm-9">
        <input class="form-control" type="email" id="email"
               minlength="5"
               ngControl="email" #control="ngForm">
        <span class="help-block" *ngIf="!control.valid && control.dirty">
            este campo debe ser mayor que 5
        </span>
    </div>
</div>

I want to create a component like this:

<control-validation>
    <label>User:</label>
    <input class="form-control" type="email" id="email"
                   minlength="5"
                   ngControl="email" #control="ngForm">
    <error-messages>
            <span class="help-block" *ngIf="!control.valid && control.dirty">
                este campo debe ser mayor que 5
            </span>
    </error-messages>
</control-validation>

I have a problem with ngClass, the parent directive should know what's the input reference name. How can I select the input and get the reference name "#control" and use it in the parent component in order to ngClass works properly?

I accept any other solutions to create a component that encapsulates any input validation according to this css.

Upvotes: 1

Views: 1723

Answers (1)

Thierry Templier
Thierry Templier

Reputation: 202206

In fact, I would use another approach. What is specific here is the input. If you want to modularize the repetitive logic (the label, the error messages and the class), I would create a dedicated component that leverages the ng-content of Angular2.

Let's call the component field. I would use it this way:

<field label="User">
  <input class="form-control" type="email" id="email"
               minlength="5"
               ngControl="email" #control="ngForm">
</field>

At the beginning the component template could be the following:

<div class="form-group">
  <label>{{label}}</label>
  <div class="col-sm-9">
    <ng-content ></ng-content>
  </div>
</div>

You need to define an input for the label into your component:

@Component({
  selector: 'field',
  template: `
    (...)
  `
})
export class FieldComponent {
  @Input()
  private label:string;
}

Then you need to reference the control associated with the input within the ng-content using @ContentChild:

@Component({
  (...)
})
export class FieldComponent {
  (...)

  @ContentChild(NgControlName)
  private control:NgControlName;
}

This way you will be able to use it (control) within the component template to check the validity of the control:

@Component({
  selector: 'field',
  template: `
    <div class="form-group" [ngClass]="{'has-error': !control.valid && control.dirty, 'has-success': control.valid && control.dirty}">
      <label class="col-sm-3 control-label">{{label}}</label>
      <div class="col-sm-9">
        <ng-content></ng-content>
        <span class="help-block"*ngIf="!control.valid && control.dirty">
          este campo debe ser mayor que 5
        </span>
      </div>
    </div>
  `
})
export class FieldComponent {
  (...)
}

With this approach, you will be able to use the field component for other inputs without having to duplicate code...

See this plunkr: https://plnkr.co/edit/rGpGCnyrEm3Q9Ezbj5em?p=preview.

See this article (section "Form component for fields" for more details:

Upvotes: 3

Related Questions