表单字段前缀/后缀/图标来自组件未正确呈现

时间:2018-01-13 22:56:57

标签: angular angular-material angular-material2

我创建了以下组件,以避免重复在表单字段中添加前缀/后缀/图标的样板html:

Measurements.aggregate([
  {"$match" : {"createdAt":{"$gt":new Date(Date.now() - 24*60*60 * 1000)}}},
  {"$sort":{"createdAt":-1}},
  {"$group":{
    "_id":{"$hour":"$createdAt"},
    "first":{"$first":"$$ROOT"},
    "last":{"$last":"$$ROOT"}
  }}
])

目标是像这样的HTML:

@Component({
    selector: "fancy-input",
    template: `
        <span matPrefix>$&nbsp;</span>
        <span matSuffix>.00</span>
        <mat-icon matSuffix>'visibility'</mat-icon>
        `,
})
export class FancyInputComponent {}

会产生这个: enter image description here

但是,似乎<mat-form-field style="width: 100%"> <input matInput placeholder="How much?" formControlName="amount"> <fancy-input></fancy-input> </mat-form-field> 元素仍然存在于DOM中,这导致前缀,后缀和图标无法正确呈现:

enter image description here

有没有办法从DOM中替换/删除<fancy-input>元素?

我尝试将选择器更改为属性(即<fancy-input>)。如果我然后将其添加到"[fancy-input]"元素,则输出将呈现为该元素的子元素,并将被忽略。我无法将属性添加到input,因为它已经是一个组件。

有没有更好的方法来实现这一目标?

1 个答案:

答案 0 :(得分:1)

一种可能性是将整个mat-form-field包含在花哨的自定义组件中,如this stackblitz所示。通过调整this answer中给出的模板和代码,添加width样式属性并处理input事件,获得了自定义组件的以下代码。请注意,该组件可以容纳额外的内容。

import { Component, Input, ViewChild, ElementRef, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms';

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => FancyInputComponent),
  multi: true
};

@Component({
  selector: "fancy-input",
  template: `
    <mat-form-field [style.width]="width">
      <input #input matInput type="text" placeholder="{{placeholder}}" [value]="value" (input)="onInput($event, input.value)">
      <span matPrefix>$&nbsp;</span>
      <span matSuffix>.00</span>
      <mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility' : 'visibility_off'}}</mat-icon>
      <ng-content></ng-content>
    </mat-form-field>`,
  providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],
})
export class FancyInputComponent {

  // for mat-icon visibility
  hide = true;

  // width style attribute of the input control
  @Input() width: string;

  // placeholder input
  @Input() placeholder: string;

  //current form control input. helpful in validating and accessing form control
  @Input() c: FormControl = new FormControl();

  // set true if we need not show the asterisk in red color
  @Input() optional: boolean = false;

  //@Input() v:boolean = true; // validation input. if false we will not show error message.

  // errors for the form control will be stored in this array
  errors: Array<any> = ['This field is required'];

  // get reference to the input element
  @ViewChild('input') inputRef: ElementRef;

  //Lifecycle hook. angular.io for more info
  ngAfterViewInit() {
    // RESET the custom input form control UI when the form control is RESET
    this.c.valueChanges.subscribe(
      () => {
        // check condition if the form control is RESET
        if (this.c.value == "" || this.c.value == null || this.c.value == undefined) {
          this.innerValue = "";
          this.inputRef.nativeElement.value = "";
        }
      }
    );
  }

  //The internal data model for form control value access
  private innerValue: any = '';

  // event fired when input value is changed . later propagated up to the form control using the custom value accessor interface
  onInput(e: Event, value: any) {
    //set changed value
    this.innerValue = value;
    // propagate value into form control using control value accessor interface
    this.propagateChange(this.innerValue);

    //reset errors 
    this.errors = [];
    //setting, resetting error messages into an array (to loop) and adding the validation messages to show below the field area
    for (var key in this.c.errors) {
      if (this.c.errors.hasOwnProperty(key)) {
        if (key === "required") {
          this.errors.push("This field is required");
        } else {
          this.errors.push(this.c.errors[key]);
        }
      }
    }
  }

  //get accessor
  get value(): any {
    return this.innerValue;
  };

  //set accessor including call the onchange callback
  set value(v: any) {
    if (v !== this.innerValue) {
      this.innerValue = v;
    }
  }

  //propagate changes into the custom form control
  propagateChange = (_: any) => { }

  //From ControlValueAccessor interface
  writeValue(value: any) {
    console.log(value);
    this.innerValue = value;
  }

  //From ControlValueAccessor interface
  registerOnChange(fn: any) {
    this.propagateChange = fn;
  }

  //From ControlValueAccessor interface
  registerOnTouched(fn: any) {
  }
}

父组件会将其包含在其模板中:

<div [formGroup]="form" class="example-container">
    <fancy-input formControlName="amount1" [width]="'100%'" [placeholder]="'My amount'" [c]="form.controls.amount1" [(ngModel)]="amount1Value"></fancy-input>
    <fancy-input formControlName="amount2" [width]="'100%'" [placeholder]="'Your amount'" [c]="form.controls.amount2" [(ngModel)]="amount2Value">
        <span>Other stuff</span>
    </fancy-input>
</div>

使用相应的代码:

export class ParentComponent {

  amount1Value = 72.18;
  amount2Value = "";

  form = new FormGroup({
    amount1: new FormControl(),
    amount2: new FormControl(),
  });  
}