将 ngModel 绑定到复杂数据
Binding ngModel to complex data
一段时间以来,我一直在研究是否以及如何将复杂模型绑定到 ngModel。有一些文章展示了如何处理简单数据(例如字符串),例如 this。但我想做的更复杂。假设我有一个 class:
export class MyCoordinates {
longitude: number;
latitude: number;
}
现在我要在应用的多个地方使用它,所以我想把它封装成一个组件:
<coordinates-form></coordinates-form>
我还想使用 ngModel 将此模型传递给组件,以利用 angular 表单之类的东西,但到目前为止没有成功。这是一个例子:
<form #myForm="ngForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name</label>
<input type="text" [(ngModel)]="model.name" name="name">
</div>
<div class="form-group">
<label for="coordinates">Coordinates</label>
<coordinates-form [(ngModel)]="model.coordinates" name="coordinates"></coordinates-form>
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
实际上可以这样做还是我的方法完全错误?现在我已经决定使用具有正常输入和更改时发出事件的组件,但我觉得它会很快变得混乱。
import {
Component,
Optional,
Inject,
Input,
ViewChild,
} from '@angular/core';
import {
NgModel,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { ValueAccessorBase } from '../Base/value-accessor';
import { MyCoordinates } from "app/Models/Coordinates";
@Component({
selector: 'coordinates-form',
template: `
<div>
<label>longitude</label>
<input
type="number"
[(ngModel)]="value.longitude"
/>
<label>latitude</label>
<input
type="number"
[(ngModel)]="value.latitude"
/>
</div>
`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: CoordinatesFormComponent,
multi: true,
}],
})
export class CoordinatesFormComponent extends ValueAccessorBase<MyCoordinates> {
@ViewChild(NgModel) model: NgModel;
constructor() {
super();
}
}
ValueAccessorBase:
import {ControlValueAccessor} from '@angular/forms';
export abstract class ValueAccessorBase<T> implements ControlValueAccessor {
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
touch() {
this.touched.forEach(f => f());
}
}
用法:
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)">
<coordinates-form
required
hexadecimal
name="coordinatesModel"
[(ngModel)]="coordinatesModel">
</coordinates-form>
<button type="Submit">Submit</button>
</form>
我遇到的错误 Cannot read property 'longitude' of undefined
。对于简单的模型,如字符串或数字,它可以正常工作。
value
属性 首先是 undefined
。
要解决此问题,您需要像这样更改绑定:
[ngModel]="value?.longitude" (ngModelChange)="value.longitude = $event"
并将其更改为 latitude
[ngModel]="value?.latitude" (ngModelChange)="value.latitude = $event"
更新
刚刚注意到您在设置器中 运行ning onChange 事件,因此您需要更改参考:
[ngModel]="value?.longitude" (ngModelChange)="handleInput('longitude', $event)"
[ngModel]="value?.latitude" (ngModelChange)="handleInput('latitude', $event)"
handleInput(prop, value) {
this.value[prop] = value;
this.value = { ...this.value };
}
Plunker Example with google map
更新 2
在处理自定义表单控件时需要实现这个接口:
export interface ControlValueAccessor {
/**
* Write a new value to the element.
*/
writeValue(obj: any): void;
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: any): void;
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: any): void;
/**
* This function is called when the control status changes to or from "DISABLED".
* Depending on the value, it will enable or disable the appropriate DOM element.
*
* @param isDisabled
*/
setDisabledState?(isDisabled: boolean): void;
}
这是一个最小的实现:
export abstract class ValueAccessorBase<T> implements ControlValueAccessor {
// view => control
onChange = (value: T) => {};
onTouched = () => {};
writeValue(value: T) {
// control -> view
}
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
}
它适用于任何类型的值。只需为您的案例实施它。
你不需要像这样的数组(见更新Plunker)
private changed = new Array<(value: T) => void>();
当组件获得新值时,它会 运行 writeValue
您需要更新一些将在您的自定义模板中使用的值。在您的示例中,您正在更新 value
属性,它与模板中的 ngModel
一起使用。
- 在我的示例中,我正在绘制新标记。
DefaultValueAccessor
只是更新 value
属性 https://github.com/angular/angular/blob/4.2.0-rc.0/packages/forms/src/directives/default_value_accessor.ts#L76
Datepicker
in angular2 material 正在设置内部 value
就像您所做的 https://github.com/angular/material2/blob/123d7eced4b4f808fc03c945504d68280752d533/src/lib/datepicker/datepicker-input.ts#L202
当您需要将更改传播到 AbstractControl
时,您必须调用您在 registerOnChange
.
中注册的 onChange
方法
我写了this.value = { ...this.value };
因为它只是
this.value = Object.assign({}, this.value)
它将在您调用 onChange
方法的地方调用 setter
另一种方法是直接调用onChange,通常使用
this.onChange(this.value);
DefaultValueAccessor
https://github.com/angular/angular/blob/4.2.0-rc.0/packages/forms/src/directives/default_value_accessor.ts#L88
您可以在自定义组件中做任何您喜欢的事情。它可以有任何模板和任何嵌套组件。但是您必须为 ControlValueAccessor
实现逻辑才能使用 angular 表单。
如果你打开一些库,如 angular2 material 或 primeng,你可以找到很多如何实现此类控件的示例
一段时间以来,我一直在研究是否以及如何将复杂模型绑定到 ngModel。有一些文章展示了如何处理简单数据(例如字符串),例如 this。但我想做的更复杂。假设我有一个 class:
export class MyCoordinates {
longitude: number;
latitude: number;
}
现在我要在应用的多个地方使用它,所以我想把它封装成一个组件:
<coordinates-form></coordinates-form>
我还想使用 ngModel 将此模型传递给组件,以利用 angular 表单之类的东西,但到目前为止没有成功。这是一个例子:
<form #myForm="ngForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name</label>
<input type="text" [(ngModel)]="model.name" name="name">
</div>
<div class="form-group">
<label for="coordinates">Coordinates</label>
<coordinates-form [(ngModel)]="model.coordinates" name="coordinates"></coordinates-form>
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
实际上可以这样做还是我的方法完全错误?现在我已经决定使用具有正常输入和更改时发出事件的组件,但我觉得它会很快变得混乱。
import {
Component,
Optional,
Inject,
Input,
ViewChild,
} from '@angular/core';
import {
NgModel,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { ValueAccessorBase } from '../Base/value-accessor';
import { MyCoordinates } from "app/Models/Coordinates";
@Component({
selector: 'coordinates-form',
template: `
<div>
<label>longitude</label>
<input
type="number"
[(ngModel)]="value.longitude"
/>
<label>latitude</label>
<input
type="number"
[(ngModel)]="value.latitude"
/>
</div>
`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: CoordinatesFormComponent,
multi: true,
}],
})
export class CoordinatesFormComponent extends ValueAccessorBase<MyCoordinates> {
@ViewChild(NgModel) model: NgModel;
constructor() {
super();
}
}
ValueAccessorBase:
import {ControlValueAccessor} from '@angular/forms';
export abstract class ValueAccessorBase<T> implements ControlValueAccessor {
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
touch() {
this.touched.forEach(f => f());
}
}
用法:
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)">
<coordinates-form
required
hexadecimal
name="coordinatesModel"
[(ngModel)]="coordinatesModel">
</coordinates-form>
<button type="Submit">Submit</button>
</form>
我遇到的错误 Cannot read property 'longitude' of undefined
。对于简单的模型,如字符串或数字,它可以正常工作。
value
属性 首先是 undefined
。
要解决此问题,您需要像这样更改绑定:
[ngModel]="value?.longitude" (ngModelChange)="value.longitude = $event"
并将其更改为 latitude
[ngModel]="value?.latitude" (ngModelChange)="value.latitude = $event"
更新
刚刚注意到您在设置器中 运行ning onChange 事件,因此您需要更改参考:
[ngModel]="value?.longitude" (ngModelChange)="handleInput('longitude', $event)"
[ngModel]="value?.latitude" (ngModelChange)="handleInput('latitude', $event)"
handleInput(prop, value) {
this.value[prop] = value;
this.value = { ...this.value };
}
Plunker Example with google map
更新 2
在处理自定义表单控件时需要实现这个接口:
export interface ControlValueAccessor {
/**
* Write a new value to the element.
*/
writeValue(obj: any): void;
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: any): void;
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: any): void;
/**
* This function is called when the control status changes to or from "DISABLED".
* Depending on the value, it will enable or disable the appropriate DOM element.
*
* @param isDisabled
*/
setDisabledState?(isDisabled: boolean): void;
}
这是一个最小的实现:
export abstract class ValueAccessorBase<T> implements ControlValueAccessor {
// view => control
onChange = (value: T) => {};
onTouched = () => {};
writeValue(value: T) {
// control -> view
}
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
}
它适用于任何类型的值。只需为您的案例实施它。
你不需要像这样的数组(见更新Plunker)
private changed = new Array<(value: T) => void>();
当组件获得新值时,它会 运行 writeValue
您需要更新一些将在您的自定义模板中使用的值。在您的示例中,您正在更新 value
属性,它与模板中的 ngModel
一起使用。
- 在我的示例中,我正在绘制新标记。
DefaultValueAccessor
只是更新value
属性 https://github.com/angular/angular/blob/4.2.0-rc.0/packages/forms/src/directives/default_value_accessor.ts#L76Datepicker
in angular2 material 正在设置内部value
就像您所做的 https://github.com/angular/material2/blob/123d7eced4b4f808fc03c945504d68280752d533/src/lib/datepicker/datepicker-input.ts#L202
当您需要将更改传播到 AbstractControl
时,您必须调用您在 registerOnChange
.
onChange
方法
我写了this.value = { ...this.value };
因为它只是
this.value = Object.assign({}, this.value)
它将在您调用 onChange
方法的地方调用 setter
另一种方法是直接调用onChange,通常使用
this.onChange(this.value);
DefaultValueAccessor
https://github.com/angular/angular/blob/4.2.0-rc.0/packages/forms/src/directives/default_value_accessor.ts#L88
您可以在自定义组件中做任何您喜欢的事情。它可以有任何模板和任何嵌套组件。但是您必须为 ControlValueAccessor
实现逻辑才能使用 angular 表单。
如果你打开一些库,如 angular2 material 或 primeng,你可以找到很多如何实现此类控件的示例