由于我使用了大量相同指令和.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中以更好的方式完成吗?
答案 0 :(得分:34)
前段时间我遇到了同样的问题,想要分享一个适用于Angular 2+的最小例子。
对于较新的Angular版本,有一种简化的方法(向下滚动)!
假设您希望在应用中的任何位置使用以下代码:
<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`。
然后我们需要定义函数writeValue
,registerOnChange
和registerOnTouched
。后两者直接调用组件的创建。这就是为什么我们需要变量(例如onChange
和onTouched
- 但你可以随意命名它们。
最后,我们需要定义一个函数,让组件知道更新底层的ngModel。我用函数updateChanges
做到了这一点。每当值发生变化时,都需要调用它,无论是从外部(这是为什么在writeValue
中调用它),还是从内部(这是为什么从html {{1调用它)的原因。 }})。
虽然第一种方法仍适用于较新版本,但您可能更喜欢以下需要较少打字的版本。
在早期,您可以通过在外部组件中添加类似的内容来实现双向绑定:
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。
否则:
将NG_VALUE_ACCESSOR
注入令牌添加到您的组件定义中:
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
...,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AppInputComponent),
multi: true
}
]
})
实施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 = () => { };
}
更改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),以便在数据服务值发生更改时随时更新。