5
votes

I want to use content projection, but I can't bring it to work.

The HTML is

<form [ngFormModel]="demoForm" (ngSubmit)="onSubmit()">
  <my-form-group myLabel="Some Label">
    <input type="text" [ngFormControl]="demoForm.controls['someInput'] [required]="true">
  </my-form-group>
</form>

The Component is

@Component({
  selector: 'my-form-group',
  template: `
    <div class="form-group">
      <label>{{myLabel}}<span *ngIf="required">&nbsp;*</span></label>
      <ng-content></ng-content>
    </div>
    `
})

export class MyFormGroup {
  @Input() myLabel: string;
}

How can I bind the *ngIf condition in the span to the required property of projected input element? The goal is to show the asterisk in the outer component, when [required] becomes true.

Edit: I made a - not working - plunkr to show what I mean.

2

2 Answers

3
votes

You could reference the control within the ng-content using the ContentChild decorator:

@Component({
  selector: 'my-form-group',
  template: `
    <div class="form-group">
      <label>{{myLabel}}<span *ngIf="required">&nbsp;*</span></label>
      <ng-content></ng-content>
    </div>
  `
})
export class MyFormGroup {
  @ContentChild(NgFormControl) state; // <------
  @Input() myLabel: string;

  ngAfterViewInit() {
    this.required = (this.state.validator === Validators.required); // <-------
  }
}

This way you will have access to the control properties and specially the validator. If you have the Validators.required one, your input is required.

What is not clear to me is that you use ngFormControl and the required attribute. I think that you should use the following rather:

this.demoForm = formBuilder.group({
  someInput: ['', Validators.required ]
});

See this question for more details:

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

Edit

After having dug a bit, you can check that the RequiredValidator directive is applied on the input:

@Component({
  selector: 'my-form-group',
  template: `
    <div class="form-group">
      <label>{{myLabel}}<span *ngIf="required">&nbsp;*</span></label>
      <ng-content></ng-content>
    </div>
  `
})
export class MyFormGroup {
  @ContentChild(RequiredValidator) requiredValidator; // <------
  @Input() myLabel: string;

  constructor(private cdr:ChangeDetectorRef) {

  }

  ngAfterViewInit() {
    this.required = (this.requiredValidator != null); // <-------
    this.cdr.detectChanges();
  }
}
0
votes

Because I got no more answers until now, in the meantime I tryed to find a solution. I found one, which is not really satisfying. I simple inject "required" also into the outer container. I don't like this solution, because a have inject the value twice.

And at the end I sticked with a new problem. But here is the code first:

<my-form-group myLabel="Hallo" [myRequired]="demoForm.controls['required'].value">
    <input type="text" [ngFormControl]="demoForm.controls['hallo']" [required]="demoForm.controls['required'].value">
    errors: {{demoForm.controls['hallo'].errors | json}}
</my-form-group>

And the component:

@Component({
  selector: 'my-form-group',
  template: `
  <div class="form-group">
    <label *ngIf="myLabel">{{myLabel}}<span *ngIf="myRequired">&nbsp;*</span></label>
    <ng-content></ng-content>
  </div>`
})
export class MyFormGroup {
  @Input() myLabel: string;
  @Input() myRequired: boolean;
  @ContentChild(NgFormControl) ctrl;
}

My next problem now is: Validation is not updated. Please see this Plunkr showing also the input's errors object. https://plnkr.co/edit/cBqSB6DX68EnqvnyqrGg?p=preview

Is there any solution for this? (or is it simply a bug?) And, if one has a better solution then mine, I would enjoy.