安装angular报错4中ngmodel为什么报错

标签:至少1个,最多5个
在 Angular 4.x 中对于使用 Template-Driven 表单场景,如果需要实现表单数据绑定。我们就需要引入 ngModel 指令。该指令用于基于 domain 模型,创建 FormControl 实例,并将创建的实例绑定到表单控件元素上。
ngModel 使用示例
<ponent.ts
@Component({
selector: 'exe-app',
template: `
&form novalidate #f="ngForm"&
Name: &input type="text" name="username" ngModel&
{{ f.value | json }}
export class AppComponent implements OnInit { }
在 &form& 表单中使用 ngModel 时,我们需要设置一个 name 属性,以便该控件可以使用该名称在表单中进行注册。
单向绑定 - [ngModel]
<ponent.ts
@Component({
selector: 'exe-app',
template: `
&form novalidate #f="ngForm"&
Name: &input type="text" name="username" [ngModel]="user.username"&
{{ user | json }}
export class AppComponent implements OnInit {
user: { username: string };
ngOnInit() {
this.user = { username: 'Semlinker' };
双向绑定 - [(ngModel)]
表单中应用
<ponent.ts
@Component({
selector: 'exe-app',
template: `
&form novalidate #f="ngForm"&
Name: &input type="text" name="username" [(ngModel)]="user.username"&
{{ user | json }}
export class AppComponent implements OnInit {
user: { username: string };
ngOnInit() {
this.user = { username: 'Semlinker' };
import { Component } from '@angular/core';
@Component({
selector: 'exe-app',
template: `
&input name="username" [(ngModel)]="username"&
{{username}}
export class AppComponent {
ngModelOptions - [ngModelOptions]
当你在使用 ngModel 时未设置 name 属性,如下所示:
&form novalidate #f="ngForm"&
Name: &input type="text" [(ngModel)]="user.username"&
当你运行时,浏览器控制台将会抛出以下异常信息:
Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions.
以上异常信息告诉我们,如果在表单标签中使用 ngModel,则必须设置 name 属性,或者在 ngModelOptions 中必须将表单控件定义为 "standalone"。依据上述异常信息,我们做如下调整:
&form novalidate #f="ngForm"&
Name: &input type="text" [(ngModel)]="user.username"
[ngModelOptions]="{standalone: true}"&
接下来我们看一下 ngModelOptions 支持的对象类型:
@Input('ngModelOptions') options: {name?: string, standalone?: boolean};
- disabled
&form novalidate #f="ngForm"&
Name: &input type="text" name="username"
[(ngModel)]="user.username" disabled="true"&
监听 ngModelChange 事件 - (ngModelChange)
<ponent.ts
@Component({
selector: 'exe-app',
template: `
&form novalidate #f="ngForm"&
Name: &input type="text" name="username" (ngModelChange)="userNameChange($event)"
[(ngModel)]="user.username"&
{{ user | json }}
export class AppComponent implements OnInit {
user: { username: string };
ngOnInit() {
this.user = { username: 'Semlinker' };
userNameChange(name: string) {
console.log(name);
获取关联的 NgModel 对象
<ponent.ts
@Component({
selector: 'exe-app',
template: `
&form novalidate #f="ngForm"&
Name: &input type="text" name="username" #userName="ngModel"
[(ngModel)]="user.username"&
{{ userName.control | json }}
export class AppComponent implements OnInit {
user: { username: string };
ngOnInit() {
this.user = { username: 'Semlinker' };
通过使用 userName="ngModel" 方式,我们可以获取表单控件关联的 NgModel 对象,进而获取控件当前控件的相关信息,如控件的当前的状态或控件验证信息等。
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
@Component({
selector: 'exe-app',
template: `
&form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate&
&input name="first" ngModel required #first="ngModel"&
&input name="last" ngModel&
&button&Submit&/button&
&p&First name value: {{ first.value }}&/p&
&p&First name valid: {{ first.valid }}&/p&
&p&Form value: {{ f.value | json }}&/p&
&p&Form valid: {{ f.valid }}&/p&
export class AppComponent {
onSubmit(f: NgForm) {
console.log(f.value);
// { first: '', last: '' }
console.log(f.valid);
ngModel 指令详解
ngModel 指令定义
@Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
formControlBinding 定义
export const formControlBinding: any = {
provide: NgControl,
useExisting: forwardRef(() =& NgModel)
selector 中 [ngModel]:not([formControlName]):not([formControl])
表示该指令只应用于 Template-Driven 表单中。
exportAs - 表示可以使用 first="ngModel" 语法获取 NgModel 对象
ngModel 指令输入与输出属性
@Input() name:
@Input('disabled') isDisabled:
@Input('ngModel') model:
@Input('ngModelOptions') options: {name?: string, standalone?: boolean};
@Output('ngModelChange') update = new EventEmitter();
NgModel 类
// angular2/packages/forms/src/directives/ng_model.ts
export class NgModel extends NgControl implements OnChanges,
OnDestroy {
/** @internal */
_control = new FormControl(); // 创建FormControl对象
/** @internal */
_registered = // 用于标识控件是否已注册
viewModel: // 用于保存前一次model的值
NgModel 构造函数
constructor(
@Optional() @Host() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array&Validator|ValidatorFn&,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
Array&AsyncValidator|AsyncValidatorFn&,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) {
this._parent =
this._rawValidators = validators || [];
this._rawAsyncValidators = asyncValidators || [];
this.valueAccessor = selectValueAccessor(this, valueAccessors);
@Optional() - 表示该依赖对象是可选的
@Host() - 表示从宿主元素注入器获取依赖对象
@Self() - 表示从当前注入器获取依赖对象
@Inject() - 用于注入 Token (new InjectionToken) 对应的非 Type 类型依赖对象
构造函数执行的操作:
获取 ControlContainer (控件容器)对象
获取控件上的同步验证器
获取控件上的异步验证器
获取控件上的 ControlValueAccessor
NgModel 生命周期钩子
ngOnChanges
ngOnChanges(changes: SimpleChanges) {
this._checkForErrors();
if (!this._registered) this._setUpControl();
if ('isDisabled' in changes) {
this._updateDisabled(changes);
if (isPropertyUpdated(changes, this.viewModel)) {
this._updateValue(this.model);
this.viewModel = this.
_checkForErrors()
private _checkForErrors(): void {
if (!this._isStandalone()) {
this._checkParentType();
this._checkName();
// 判断是否设置standalone属性
private _isStandalone(): boolean {
return !this._parent || (this.options && this.options.standalone);
* 1.ngModel指令不能与formGroupName或formArrayName指令一起使用,需改用
* formControlName或调整ngModel的父控件使用的指令为ngModelGroup。
* 2.ngModel不能被注册到使用formGroup指令的表单中,需改用formControlName或设置
* ngModelOptions对象中的standalone属性,避免注册该控件。
private _checkParentType(): void {
if (!(this._parent instanceof NgModelGroup) &&
this._parent instanceof AbstractFormGroupDirective) {
TemplateDrivenErrors.formGroupNameException();
} else if (!(this._parent instanceof NgModelGroup) &&
!(this._parent instanceof NgForm)) {
TemplateDrivenErrors.modelParentException();
* 验证是否设置name属性
* 如果在表单标签中使用 ngModel,则必须设置 name 属性,或者在ngModelOptions中必须将
* 表单控件定义为"standalone"。
* &input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone:
private _checkName(): void {
if (this.options && this.options.name) this.name = this.options.
if (!this._isStandalone() && !this.name) {
TemplateDrivenErrors.missingNameException();
_setUpControl()
// 初始化控件
private _setUpControl(): void {
this._isStandalone() ? this._setUpStandalone() :
// 在ControlContainer所属的form中注册该控件
this.formDirective.addControl(this);
this._registered = // 标识已注册
// 若设置standalone属性,则初始化该控件,并更新控件的值和验证状态
private _setUpStandalone(): void {
setUpControl(this._control, this);
this._control.updateValueAndValidity({emitEvent: false});
// 获取ControlContainer所属的form
get formDirective(): any {
return this._parent ? this._parent.formDirective :
_updateDisabled()
若设置 isDisabled 输入属性,则更新控件的 disabled 属性:
// 更新控件的disabled状态
private _updateDisabled(changes: SimpleChanges) {
// 获取disabled输入属性的当前值
const disabledValue = changes['isDisabled'].currentV
// 判断是否设置为disabled
const isDisabled = disabledValue === '' ||
(disabledValue && disabledValue !== 'false');
resolvedPromise.then(() =& {
if (isDisabled && !this.control.disabled) {
this.control.disable(); // 禁用控件
} else if (!isDisabled && this.control.disabled) {
this.control.enable(); // 启用控件
isPropertyUpdated()
// 判断属性是否更新
export function isPropertyUpdated(changes: {[key: string]: any},
viewModel: any): boolean {
if (!changes.hasOwnProperty('model')) // @Input('ngModel') model:
const change = changes['model'];
if (change.isFirstChange()) // 判断是否首次改变
return !looseIdentical(viewModel, change.currentValue);
// JS has NaN !== NaN
export function looseIdentical(a: any, b: any): boolean {
return a === b || typeof a === 'number' && typeof b === 'number' && isNaN(a)
&& isNaN(b);
_updateValue()
// 更新控件的值
private _updateValue(value: any): void {
resolvedPromise.then(
() =& { this.control.setValue(value, {emitViewToModelChange: false});
const resolvedPromise = Promise.resolve(null);
ngOnDestroy()
// 指令销毁时,从formDirective中移除该控件
ngOnDestroy(): void {
this.formDirective && this.formDirective.removeControl(this);
NgModel 方法
get control(): FormControl
// 获取控件
get control(): FormControl { return this._ }
/** @internal */
_control = new FormControl();
get path(): string[]
// 获取控件的访问路径
get path(): string[] {
return this._parent ? controlPath(this.name, this._parent) : [this.name];
get validator(): ValidatorFn
// 获取同步验证器
get validator(): ValidatorFn {
return composeValidators(this._rawValidators);
export interface ValidatorFn { (c: AbstractControl): ValidationErrors| }
get asyncValidator(): AsyncValidatorFn
// 获取异步验证器
get asyncValidator(): AsyncValidatorFn {
return composeAsyncValidators(this._rawAsyncValidators);
export interface AsyncValidatorFn {
(c: AbstractControl): Promise&ValidationErrors|null&|Observable&ValidationErrors|null&;
viewToModelUpdate(newValue: any): void
// 触发ngModelChange事件
viewToModelUpdate(newValue: any): void {
this.viewModel = newV
// @Output('ngModelChange') update = new EventEmitter();
this.update.emit(newValue);
NgControl 抽象类
// angular2/packages/forms/src/directives/ng_control.ts
// 所有控件指令都需继承的基类,绑定FormControl对象至DOM元素
export abstract class NgControl extends AbstractControlDirective {
/** @internal */
_parent: ControlContainer =
name: string =
valueAccessor: ControlValueAccessor =
/** @internal */
_rawValidators: Array&Validator|ValidatorFn& = [];
/** @internal */
_rawAsyncValidators: Array&AsyncValidator|AsyncValidatorFn& = [];
get validator(): ValidatorFn { return &ValidatorFn&unimplemented(); }
get asyncValidator(): AsyncValidatorFn { return &AsyncValidatorFn&unimplemented(); }
abstract viewToModelUpdate(newValue: any):
AbstractControlDirective 抽象类
// angular2/packages/forms/src/directives/abstract_control_directive.ts
export abstract class AbstractControlDirective {
// 获取控件
get control(): AbstractControl { throw new Error('unimplemented'); }
// 获取控件的值
get value(): any { return this.control ? this.control.value : }
// 控件控件的验证状态 - valid、invalid、pending
get valid(): boolean { return this.control ? this.control.valid : }
get invalid(): boolean { return this.control ? this.control.invalid : }
get pending(): boolean { return this.control ? this.control.pending : }
get pristine(): boolean { return this.control ? this.control.pristine : }
get dirty(): boolean { return this.control ? this.control.dirty : }
get touched(): boolean { return this.control ? this.control.touched : }
get untouched(): boolean { return this.control ? this.control.untouched : }
get disabled(): boolean { return this.control ? this.control.disabled : }
get enabled(): boolean { return this.control ? this.control.enabled : }
// 获取控件验证异常对象
get errors(): ValidationErrors|null {
return this.control ? this.control.errors :
// 获取statusChanges对象
get statusChanges(): Observable&any& {
return this.control ? this.control.statusChanges :
// 获取valueChanges对象
get valueChanges(): Observable&any& {
return this.control ? this.control.valueChanges :
// 获取控件路径
get path(): string[] { }
// 重设控件的值
reset(value: any = undefined): void {
if (this.control) this.control.reset(value);
// 判断是否path路径对应的控件,是否存在errorCode对应的错误
hasError(errorCode: string, path: string[] = null): boolean {
return this.control ? this.control.hasError(errorCode, path) :
// 获取path路径对应的控件,参数errorCode对应的错误
getError(errorCode: string, path: string[] = null): any {
return this.control ? this.control.getError(errorCode, path) :
input 指令
input 指令定义
@Directive({
selector:`
input:not([type=checkbox])[formControlName],textarea[formControlName],
input:not([type=checkbox])[formControl],textarea[formControl],
input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]
'(input)': '_handleInput($event.target.value)',
'(blur)': 'onTouched()',
'(compositionstart)': '_compositionStart()',
'(compositionend)': '_compositionEnd($event.target.value)'
providers: [DEFAULT_VALUE_ACCESSOR]
compositionstart - 事件触发于一段文字的输入之前 (类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)。
compositionend - 事件触发于完成文本段落输入或取消输入
compositionstart、compositionend 的实际应用,请参考 -
DEFAULT_VALUE_ACCESSOR
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() =& DefaultValueAccessor),
multi: true
DefaultValueAccessor
export class DefaultValueAccessor implements ControlValueAccessor {
onChange = (_: any) =& {};
onTouched = () =& {};
/** Whether the user is creating a composition string (IME events). */
private _composing =
constructor(
private _renderer: Renderer, // 注入Renderer对象
private _elementRef: ElementRef,
@Optional() @Inject(COMPOSITION_BUFFER_MODE)
private _compositionMode: boolean) {
if (this._compositionMode == null) {
this._compositionMode = !_isAndroid();
// 将模型中的新值写入视图或DOM元素属性中
writeValue(value: any): void {
const normalizedValue = value == null ? '' :
this._renderer.setElementProperty(this._elementRef.nativeElement,
'value', normalizedValue);
// 设置当控件接收到change事件后,调用的函数
registerOnChange(fn: (_: any) =& void): void { this.onChange = }
// 设置当控件接收到touched事件后,调用的函数
registerOnTouched(fn: () =& void): void { this.onTouched = }
// 设置控件的Disabled状态
setDisabledState(isDisabled: boolean): void {
this._renderer.setElementProperty(this._elementRef.nativeElement,
'disabled', isDisabled);
// 处理input事件
_handleInput(value: any): void {
if (!this._compositionMode || (this._compositionMode && !this._composing)) {
this.onChange(value);
// 处理compositionstart事件
_compositionStart(): void { this._composing = }
// 处理compositionend事件
_compositionEnd(value: any): void {
this._composing =
this._compositionMode && this.onChange(value);
export const COMPOSITION_BUFFER_MODE = new InjectionToken&boolean&
('CompositionEventMode');
// 用于判断是否处于安卓平台,composition事件在iOS和Android存在兼容性
function _isAndroid(): boolean {
const userAgent = getDOM() ? getDOM().getUserAgent() : '';
return /android (\d+)/.test(userAgent.toLowerCase());
为了能够支持跨平台,Angular 通过抽象层封装了不同平台的差异,统一了 API 接口。如定义了抽象类 Renderer 、抽象类 RootRenderer 等。此外还定义了以下引用类型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。
了解详细的信息,请查看 -
另外看完上面的代码,不知道读者有没有以下的疑问:
writeValue() 方法什么时候调用?
registerOnChange() 什么时候调用?
registerOnTouched() 什么时候调用?
为了解开这些疑惑我们就需要分析一下,一个很重要的方法 - setUpControl()。我们先来看一下 setUpControl() 的调用的时机点:
NgModel ngOnChanges 生命周期钩子
ngOnChanges(changes: SimpleChanges) {
if (!this._registered) this._setUpControl();
_setUpControl() 方法
private _setUpControl(): void {
this._isStandalone() ? this._setUpStandalone() :
// 在ControlContainer所属的form中注册该控件
this.formDirective.addControl(this);
this._registered = // 标识已注册
_setUpControl() 方法内部,先判断控件有设置 standalone 属性,如果有的话,则调用 _setUpStandalone() 方法:
// 若设置standalone属性,则初始化该控件,并更新控件的值和验证状态
private _setUpStandalone(): void {
setUpControl(this._control, this); // 调用时机点一
this._control.updateValueAndValidity({emitEvent: false});
如果没有设置 standalone 属性,则调用 this.formDirective.addControl(this),这个方法存在于我们的 form 指令中,我们直接看一下具体实现:
addControl(dir: NgModel): void {
resolvedPromise.then(() =& {
const container = this._findContainer(dir.path);
dir._control = &FormControl&container.registerControl(dir.name, dir.control);
setUpControl(dir.control, dir); // 调用时机点二
dir.control.updateValueAndValidity({emitEvent: false});
搞清楚 setUpControl() 调用的时机点,是时候分析一下 setUpControl() 方法的具体实现了。
setUpControl()
// angular2/packages/forms/src/directives/shared.ts
export function setUpControl(control: FormControl, dir: NgControl): void {
if (!control) _throwError(dir, 'Cannot find control with');
* NgModel构造函数
* @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]
* this.valueAccessor = selectValueAccessor(this, valueAccessors);
// 判断控件是否实现ControlValueAccessor接口
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
// 组合同步验证器
control.validator = pose([control.validator, dir.validator]);
// 组合异步验证器
control.asyncValidator = poseAsync([control.asyncValidator,
dir.asyncValidator]);
// 该方法用于将模型中的新值写入视图或 DOM 属性中
dir.valueAccessor.writeValue(control.value);
// view -& model
* @Directive({
selector: 'input:not([type=checkbox])[formControlName],...',
'(input)': '_handleInput($event.target.value)'
providers: [DEFAULT_VALUE_ACCESSOR]
* export class DefaultValueAccessor implements ControlValueAccessor {
// 下面就是调用该方法
registerOnChange(fn: (_: any) =& void): void { this.onChange = }
// input事件触发后,调用该方法
_handleInput(value: any): void {
if (!this._compositionMode || (this._compositionMode && !this._composing)) {
this.onChange(value); //调用下面注册的onChange函数
dir.valueAccessor.registerOnChange((newValue: any) =& {
* ngModel指令 - viewToModelUpdate() 方法
* viewToModelUpdate(newValue: any): void {
this.viewModel = newV // 更新viewModel
* // @Output('ngModelChange') update = new EventEmitter();
this.update.emit(newValue); // 触发ngModelChange事件
dir.viewToModelUpdate(newValue);
control.markAsDirty();
* setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange,
emitViewToModelChange}: {
onlySelf?: boolean,
emitEvent?: boolean,
emitModelToViewChange?: boolean,
emitViewToModelChange?: boolean
* } = {}): void {
this._value =
if (this._onChange.length && emitModelToViewChange !== false) {
this._onChange.forEach((changeFn) =& changeFn(this._value,
emitViewToModelChange !== false));
this.updateValueAndValidity({onlySelf, emitEvent});
control.setValue(newValue, {emitModelToViewChange: false}); // 更新控件的值
// touched
dir.valueAccessor.registerOnTouched(() =& control.markAsTouched());
* control = new FormControl();
* control - _onChange 属性
* _onChange: Function[] = [];
* control - registerOnChange() 方法
* registerOnChange(fn: Function): void { this._onChange.push(fn); }
control.registerOnChange((newValue: any, emitModelEvent: boolean) =& {
// control -& view
* writeValue(value: any): void {
const normalizedValue = value == null ? '' :
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value',
normalizedValue);
dir.valueAccessor.writeValue(newValue);
// control -& ngModel
* ngModel指令 - viewToModelUpdate() 方法
* viewToModelUpdate(newValue: any): void {
this.viewModel = newV // 更新viewModel
* // @Output('ngModelChange') update = new EventEmitter();
this.update.emit(newValue); // 触发ngModelChange事件
if (emitModelEvent) dir.viewToModelUpdate(newValue);
// 当控件状态变成 DISABLED 或从 DISABLED 状态变化成 ENABLE 状态时,会调用该函数。该函数会根据参数
// 值,启用或禁用指定的 DOM 元素
if (dir.valueAccessor.setDisabledState) {
control.registerOnDisabledChange(
(isDisabled: boolean) =& { dir.valueAccessor.setDisabledState(isDisabled); });
// re-run validation when validator binding changes, e.g. minlength=3 -& minlength=4
dir._rawValidators.forEach((validator: Validator | ValidatorFn) =& {
if ((&Validator&validator).registerOnValidatorChange)
(&Validator&validator).registerOnValidatorChange(() =&
control.updateValueAndValidity());
dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) =& {
if ((&Validator&validator).registerOnValidatorChange)
(&Validator&validator).registerOnValidatorChange(() =&
control.updateValueAndValidity());
最后我们再看一下 ControlValueAccessor 接口:
ControlValueAccessor
// angular2/packages/forms/src/directives/control_value_accessor.ts
export interface ControlValueAccessor {
writeValue(obj: any):
registerOnChange(fn: any):
registerOnTouched(fn: any):
setDisabledState?(isDisabled: boolean):
writeValue(obj: any):该方法用于将模型中的新值写入视图或 DOM 属性中
registerOnChange(fn: any):设置当控件接收到 change 事件后,调用的函数
registerOnTouched(fn: any):设置当控件接收到 touched 事件后,调用的函数
setDisabledState?(isDisabled: boolean):当控件状态变成 DISABLED 或从 DISABLED 状态变化成 ENABLE 状态时,会调用该函数。该函数会根据参数值,启用或禁用指定的 DOM 元素
了解 ControlValueAccessor 的详细信息,可以参考 -
明天补充图示说明哈,能够理解的同学请直接略过。
3 收藏&&|&&18
你可能感兴趣的文章
4 收藏,1.1k
5 收藏,1.5k
48 收藏,4.4k
本作品采用 署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
写的不错,好好研究下。
写的不错,好好研究下。
Awesome!!!
Awesome!!!
分享到微博?
你好!看起来你挺喜欢这个内容,但是你还没有注册帐号。 当你创建了帐号,我们能准确地追踪你关注的问题,在有新答案或内容的时候收到网页和邮件通知。还能直接向作者咨询更多细节。如果上面的内容有帮助,记得点赞 (????)? 表示感谢。
明天提醒我
我要该,理由是:

我要回帖

更多关于 angular.copy 报错 的文章

 

随机推荐