如何使用角度为6的ngModel创建自定义输入组件?

时间:2018-05-31 06:34:53

标签: angular

由于我使用了大量相同指令和.css类的输入,我想将重复的代码提取到某个组件,如下所示:

  @Component({
  selector: "app-input",
  template: `
    <div class="...">
      <input type="..." name="..." class="..." [(ngModel)]="value" someDirectives...>
      <label for="...">...</label>
    </div>
  `,
  ...
  })
  export class InputComponent implements OnInit {
    // some implementation connecting external ngModel with internal "value" one
  }

这里的问题是创建一个组件,它可以与ngModel一起用作普通输入:

<app-input [(ngModel)]="externalValue" ... ></app-input>

我在互联网上找到了几种可以部分或完全过时的解决方案,例如: Angular 2 custom form input 这可以在角度6中以更好的方式完成吗?

5 个答案:

答案 0 :(得分:34)

前段时间我遇到了同样的问题,想要分享一个适用于Angular 2+的最小例子。

对于较新的Angular版本,有一种简化的方法(向下滚动)!

Angular 2 +

假设您希望在应用中的任何位置使用以下代码:

<app-input-slider [(ngModel)]="inputSliderValue"></app-input-slider>

1)现在创建一个名为InputSlider的组件。

2)在input-slider.component.html中,添加以下内容:

<input type="range" [(ngModel)]="value" (ngModelChange)="updateChanges()">

3)现在我们必须在input-slider.component.ts文件中做一些工作:

import {Component, forwardRef, OnInit} from "@angular/core";
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "@angular/forms";

@Component({
    selector: "app-input-slider",
    templateUrl: "./input-slider.component.html",
    styleUrls: ["./input-slider.component.scss"],
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => InputSliderComponent),
        multi: true
    }]

})
export class InputSliderComponent implements ControlValueAccessor {

    /**
     * Holds the current value of the slider
     */
    value: number = 0;

    /**
     * Invoked when the model has been changed
     */
    onChange: (_: any) => void = (_: any) => {};

    /**
     * Invoked when the model has been touched
     */
    onTouched: () => void = () => {};

    constructor() {}

    /**
     * Method that is invoked on an update of a model.
     */
    updateChanges() {
        this.onChange(this.value);
    }

    ///////////////
    // OVERRIDES //
    ///////////////

    /**
     * Writes a new item to the element.
     * @param value the value
     */
    writeValue(value: number): void {
        this.value = value;
        this.updateChanges();
    }

    /**
     * Registers a callback function that should be called when the control's value changes in the UI.
     * @param fn
     */
    registerOnChange(fn: any): void {
        this.onChange = fn;
    }

    /**
     * Registers a callback function that should be called when the control receives a blur event.
     * @param fn
     */
    registerOnTouched(fn: any): void {
        this.onTouched = fn;
    }

}

当然,您可以使用此类添加更多功能和值检查,但我希望它能为您提供一些想法。

快速解释:

诀窍是添加提供程序NG_VALUE_ACCESSOR on the decorator of the class and implement ControlValueAccessor`。

然后我们需要定义函数writeValueregisterOnChangeregisterOnTouched。后两者直接调用组件的创建。这就是为什么我们需要变量(例如onChangeonTouched - 但你可以随意命名它们。

最后,我们需要定义一个函数,让组件知道更新底层的ngModel。我用函数updateChanges做到了这一点。每当值发生变化时,都需要调用它,无论是从外部(这是为什么在writeValue中调用它),还是从内部(这是为什么从html {{1调用它)的原因。 }})。

Angular 7 +

虽然第一种方法仍适用于较新版本,但您可能更喜欢以下需要较少打字的版本。

在早期,您可以通过在外部组件中添加类似的内容来实现双向绑定:

ngModelChange

Angular为此实现了语法糖,所以你现在可以编写

<app-input-slider [inputSliderValue]="inputSliderValue" (inputSliderValueChange)="inputSliderValue = $event"></app-input-slider>

如果您按照以下步骤操作。

1)创建一个名为<app-input-slider [(inputSliderValue)]="inputSliderValue"></app-input-slider> 的组件。

2)在InputSlider中,添加以下内容:

input-slider.component.html

3)现在我们必须在<input type="range" [(ngModel)]="inputSliderValue" (ngModelChange)="inputSliderValueChange.emit(inputSliderValue)"> 文件中做一些工作:

input-slider.component.ts

输出属性(EventEmitter)的名称必须与带有附加字符串import {Component, forwardRef, OnInit} from "@angular/core"; @Component({ selector: "app-input-slider", templateUrl: "./input-slider.component.html", styleUrls: ["./input-slider.component.scss"], providers: [] }) export class InputSliderComponent { /** * Holds the current value of the slider */ @Input() inputSliderValue: string = ""; /** * Invoked when the model has been changed */ @Output() inputSliderValueChange: EventEmitter<string> = new EventEmitter<string>(); } 的input属性相同。

如果我们比较两种方法,我们会注意到以下几点:

  • 第一种方法允许您使用Change,就像组件是任何表单元素一样。
  • 只有第一种方法允许您直接使用验证(表单)。
  • 但是第一种方法需要在组件类中进行比第二种方法更多的编码
  • 第二种方法允许您使用语法[(ngModel)]="propertyNameOutsideTheComponent"
  • 在属性上使用双向绑定

答案 1 :(得分:13)

这也可以像这样完成,当您创建双向绑定[[)]时,您可以使用相同的名称+'change'(在我们的示例中为inputModel和inputModelChange)将其绑定到函数,这样ngModel将触发inputModelChange.emit('updatedValue')时更新。并且只需在组件内部声明一次即可。

import { Component, OnInit, Output, Input, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-input',
  template: `  <input type="text" [(ngModel)]="inputModel" (ngModelChange)="inputModelChange.emit(inputModel)"/>`,
  styleUrls: ['./app-input.component.scss']
})
export class AppInputComponent implements OnInit {


  @Input() inputModel: any;
  @Output() inputModelChange = new EventEmitter<string>();

  constructor() { }

  ngOnInit() {
  }

}


<app-input [(inputModel)]="externalValue" ></app-input>

答案 2 :(得分:4)

如果您不在乎模板模型中的[ngModel]或反应形式的[formControl]来绑定变量,则可以使用omer answer

否则:

  1. NG_VALUE_ACCESSOR注入令牌添加到您的组件定义中:

    import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
    
    @Component({
       ...,
       providers: [
         {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => AppInputComponent),
            multi: true
         }
       ]
    })
    
  2. 实施ControlValueAccessor界面:

    export class AppInputComponent implements ControlValueAccessor {
    
      writeValue(obj: any): void {
        // Step 3
      }
    
      registerOnChange(fn: any): void {
        this.onChange = fn;
      }
    
      registerOnTouched(fn: any): void {
        this.onTouched = fn;
      }
    
      setDisabledState?(isDisabled: boolean): void {
      }
    
      onChange: any = () => { };
    
      onTouched: any = () => { };
    
    }
    
  3. 更改value

    private _value;
    
    public get value(){
      return this._value;
    }
    
    public set value(v){
      this._value = v;
      this.onChange(this._value);
      this.onTouched();
    }
    
    writeValue(obj: any): void {
      this._value = obj;
    }
    
    // Optional
    onSomeEventOccured(newValue){
      this.value = newValue;
    }
    

现在您可以使用<app-input [(ngModel)]="externalValue" ... ></app-input>

答案 3 :(得分:-2)

您可以使用@Input指令将externalValue传递给组件并与其绑定。

这是一个代码:

  @Component({
  selector: "app-input",
  template: `
    <div class="...">
      <input type="..." name="..." class="..." [(ngModel)]="externalValue" someDirectives...>
      <label for="...">...</label>
    </div>
  `,
  })

  export class InputComponent implements OnInit {
     @Input('externalValue') externalValue : any;
  }

在您的父组件中,您可以像以下一样使用它:

<app-input [externalValue]="externalValue" ... ></app-input>

答案 4 :(得分:-3)

您可以使用在两个组件之间进行通信的shareservice,而无需使用输入或输出,如下所示

<强>服务

import {Injectable} from '@angular/core';

@Injectable()
export class ShareService {
   public data: string;

   setData(newdata : string){
       this.data = newdata;
   }

   clearData(){
       this.data = '';
   }
}

设置值的组件

export class PageA {
    constructor(private shareService: ShareService, private router: Router){
    }
    gotoPageB(){
        this.shareService.setData("Sample data");
        this.router.navigate(['pageb']);  
    }

}

获取值的组件

export class PageB {
    constructor(private shareService: ShareService){ }

    get somedata() : string {
      return this.shareService.data;
    }
}

这里的关键是在获取值的组件中使用getter属性(本例中为PageB),以便在数据服务值发生更改时随时更新。