有一段时间以来,我一直在研究是否以及如何将复杂模型绑定到ngModel。有些文章展示了如何对简单数据(例如字符串)进行处理,例如this。但我想做的更复杂。比方说我有一个班级:
export class MyCoordinates {
longitude: number;
latitude: number;
}
现在我将在应用程序的多个位置使用它,所以我想将它封装到一个组件中:
<coordinates-form></coordinates-form>
我还想使用ngModel将此模型传递给组件,以利用角形式之类的东西,但到目前为止还没有成功。这是一个例子:
<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();
}
}
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
。对于简单的模型,如字符串或数字,它没有问题。
答案 0 :(得分:2)
value
属性首先是undefined
。
要解决此问题,您需要更改绑定,如:
[ngModel]="value?.longitude" (ngModelChange)="value.longitude = $event"
并将其更改为latitude
[ngModel]="value?.latitude" (ngModelChange)="value.latitude = $event"
刚刚注意到您在settor中运行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 };
}
<强> Updated Plunker 强>
<强> Plunker Example with google map 强>
当您处理自定义表单控件时,您需要实现此接口:
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
正在设置内部value
https://github.com/angular/material2/blob/123d7eced4b4f808fc03c945504d68280752d533/src/lib/datepicker/datepicker-input.ts#L202 当您需要将更改传播到AbstractControl
时,您必须调用onChange
中注册的registerOnChange
方法。
我写了this.value = { ...this.value };
,因为它只是
this.value = Object.assign({}, this.value)
它将调用setter,您可以在其中调用onChange
方法
另一种方法是直接调用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
实现逻辑以使用角形式。
如果你打开某些库这样的angular2材料或者primeng你可以找到很多例子来实现这样的控件