如何使用ngModel在angular 6中创建自定义输入组件?
由于我使用的输入有很多相同的指令和应用的.css类,因此我想将重复的代码提取到一些组件中,如下所示:如何使用ngModel在angular 6中创建自定义输入组件?,angular,Angular,由于我使用的输入有很多相同的指令和应用的.css类,因此我想将重复的代码提取到一些组件中,如下所示: @Component({ selector: "app-input", template: ` <div class="..."> <input type="..." name="..." class="..." [(ngModel)]="value" someDirectives...> <label for="..."
@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
}
@组件({
选择器:“应用程序输入”,
模板:`
...
`,
...
})
导出类InputComponent实现OnInit{
//外部NGM模型与内部“价值”模型连接的一些实现
}
这里的问题是创建一个组件,使其可以作为普通输入与ngModel一起使用:
<app-input [(ngModel)]="externalValue" ... ></app-input>
在angular 6中是否可以更好地实现这一点?您可以使用@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;
}
@组件({
选择器:“应用程序输入”,
模板:`
...
`,
})
导出类InputComponent实现OnInit{
@输入('externalValue')externalValue:any;
}
在父组件中,您可以像这样使用它:
<app-input [externalValue]="externalValue" ... ></app-input>
您可以使用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;
}
}
设置值的组件
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;
}
}
获取值的组件
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),以便在数据服务值更改时随时更新它 不久前我遇到了同样的问题,我想分享一个使用Angular 2+的最小示例
对于较新的角度版本,有一个简化的方法(向下滚动)
角度2+
假设您希望在应用程序中的任何位置使用以下代码:
<app-input-slider [(ngModel)]="inputSliderValue"></app-input-slider>
现在我们必须在输入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;
}
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>();
}
}
当然,您可以使用这个类添加更多的功能和值检查,但我希望它能给您带来一些想法
快速解释:
技巧是在类的decorator上添加providerNG\u VALUE\u访问器
,并实现ControlValueAccessor
然后我们需要定义函数writeValue
,registerChange
和registerTouched
。创建组件时直接调用后两个。这就是为什么我们需要修改变量(例如onChange
和onTouched
),但您可以随意命名它们
最后,我们需要定义一个函数,让组件知道如何更新底层的ngModel。我使用函数updateChanges
做到了这一点。无论是从外部(这就是为什么在writeValue
中调用它)还是从内部,只要值发生变化,都需要调用它(这就是为什么从htmlngModelChange
调用它)
角度7+
虽然第一种方法仍然适用于较新的版本,但您可能更喜欢以下需要较少键入的版本
在早期,您可以通过在外部组件中添加如下内容来实现双向绑定:
<app-input-slider [inputSliderValue]="inputSliderValue" (inputSliderValueChange)="inputSliderValue = $event"></app-input-slider>
现在我们必须在输入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;
}
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>();
}
从“@angular/core”导入{Component,forwardRef,OnInit};
@组成部分({
选择器:“应用程序输入滑块”,
templateUrl:“./input slider.component.html”,
样式URL:[“/input slider.component.scss”],
提供者:[]
})
导出类InputSliderComponent{
/**
*保存滑块的当前值
*/
@Input()inputSliderValue:string=“”;
/**
*当模型已更改时调用
*/
@Output()inputSliderValueChange:EventEmitter=新的EventEmitter();
}
重要的是,输出属性(EventEmitter)与带有附加字符串Change
的输入属性具有相同的名称
如果我们比较这两种方法,我们注意到以下几点:
<input type="range" [(ngModel)]="value" (ngModelChange)="updateChanges()">
<input type="range" [(ngModel)]="inputSliderValue (ngModelChange)="inputSliderValueChange.emit(inputSliderValue)">
- 第一种方法允许您使用
[(ngModel)]=“组件外部的PropertyName”
,就像组件是任何表单元素一样
- 只有第一种方法允许您直接使用验证(对于表单)
- 但是第一种方法比第二种方法需要更多的组件类内部编码
- 第二种方法允许对属性使用双向绑定,语法为
[(propertyNameInsideTheComponent)]=“propertyNameOutsideTheComponent”
也可以这样做,当您创建一个双向绑定[()]时,您可以使用相同的名称将其绑定到一个函数+“change”(在我们的例子中是inputModel和inputModelChange),这样,当您触发inputModelChange.emit('updatedValue')时,ngModel将更新。您只需要在组件中声明一次即可
应用程序输入.component.ts
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 {
@Input() inputModel: string;
@Output() inputModelChange = new EventEmitter<string>();
}
从'@angular/core'导入{Component,OnInit,Output,Input,EventEmitter};
@组成部分({
选择器:“应用程序输入”,
模板:``,
样式URL:['./app-input.component.scss']
})
导出类AppInputComponent{
@Input()inputModel:字符串;
@Output()inputModelChange=neweventemitter();
}
app.component.html
<app-input [(inputModel)]="externalValue"></app-input>
如果您不关心通过模板模型中的[ngModel]
或反应式表单中的[formControl]
绑定变量,则可以使用
否则:
将NG\u VALUE\u访问器注入令牌添加到组件定义中:
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 = () => { };
}
更改时管理值
:
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;
}
现在您可以使用
这可能是@Vikas Yep,有一个正在运行的演示,谢谢,看起来您还没有领会这个想法。传递值非常简单。问题是如何使用onlye[(ngModel)]返回更新值。我可以使用输出和E返回更新值