Angular2 beta:嵌套基于表单的 parent/child 组件并从父级验证
Angular2 beta: nesting form-based parent/child components and validating from parent
我正在尝试在 Angular2(Plunker 中带有 TS 的 beta 0)中实现一个具有 2 个嵌套表单的场景,每个表单由一个组件表示。
父组件是Word
,表示假字典中的单词。子成分是 WordSense
的,每个都代表父词的一种含义。
两个组件都使用模型驱动的表单,子表单使用 ngModel
将其模型的值绑定到表单控件。这样,父组件可以轻松地将其词义向下传递给子组件,剩下的由双向绑定完成。
两种形式都附加了简单的自定义验证器。除其他事项外,我不仅想在单词形式无效时禁用提交按钮,而且在其任何含义无效时也想禁用提交按钮。为此,我在正在编辑的模型中添加了一个isValid
属性,并在代码中观察形式的变化:每当发生变化时,我检查形式的valid
[=38] =] 并相应地设置模型的 属性。然后,我可以轻松地在视图和代码中的父组件级别添加一个检查,这样我就可以 post 只有当两种形式都正常时。
为了支持自定义验证和附加逻辑,我将初始代码从基于模板的表单切换为基于模型的表单;然而,一旦我启动重构代码,我就会收到几个 No Directive annotation found 错误,我不确定它们的含义。
可能我遗漏了一些明显的东西,但我是这里的新手。任何人都可以提出建议吗?你可以在这个 plunker 找到一个 repro:http://plnkr.co/edit/v9Dj5j5opJmonxEeotcR。这是它的一些基本代码:
a) 父组件:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES, FORM_PROVIDERS, WordSense],
templateUrl: `
<div>
<form [ngFormModel]="wordForm"
(ngSubmit)="onSubmit(wordForm.value)"
role="form">
<div class="form-group"
[class.has-error]="!lemma.valid && lemma.touched">
<label for="lemma">lemma</label>
<input type="text" id="lemma"
maxlength="100" required spellcheck="false"
class="form-control"
placeholder="lemma"
[ngFormControl]="wordForm.controls['lemma']">
<div *ngIf="lemma.hasError('required') && lemma.touched"
class="text-danger small">lemma required</div>
<div *ngIf="lemma.hasError('lemmaValidator') && lemma.touched"
class="text-danger small">invalid lemma</div>
</div>
...
<div class="form-group">
<table class="table table-bordered">
<tbody>
<tr *ngFor="#s of senses">
<td>
<word-sense [sense]="s" [ranks]="ranks" [fields]="fields"></word-sense>
</td>
</tr>
</tbody>
</table>
</div>
...
<button type="submit"
[ngClass]="{disabled: !wordForm.valid}"
class="btn btn-primary btn-sm">save</button>
</form>
</div>
`,
inputs: [
"word"
]
})
export class Word {
private _word: IWordModel;
public set word(value: IWordModel) {
this._word = value;
this.setFormValues();
}
public get word() {
return this._word;
}
// ...
// form
public wordForm: ControlGroup;
public lemma: Control;
public language: Control;
public class: Control;
public ranks: IPair<number>[];
public senses: ISenseViewModel[];
public fields: IFieldModel[];
constructor(private formBuilder:FormBuilder) {
// ...
this.senses = [
this.createSense()
];
// ...
// build the form
this.wordForm = this.formBuilder.group({
"lemma": ["", Validators.compose([Validators.required, LemmaValidator.isValidLemma])],
"language": ["eng", Validators.required],
"class": ["s.", Validators.required],
});
this.lemma = <Control> this.wordForm.controls["lemma"];
this.language = <Control> this.wordForm.controls["language"];
this.class = <Control> this.wordForm.controls["class"];
// ...
}
}
b) 子组件:
@Component({
selector: "word-sense",
directives: [FORM_DIRECTIVES],
template: `
<form class="form-inline" role="form" [ngFormModel]="senseForm">
<div class="form-group"
[class.has-error]="!definitionCtl.valid">
<input type="text"
class="form-control"
placeholder="definition"
[ngFormControl]="definitionCtl"
[(ngModel)]="sense.definition">
</div>
<div class="form-group"
[class.has-error]="!yearCtl.valid">
<input type="number"
class="form-control"
placeholder="date"
[ngFormControl]="yearCtl"
[(ngModel)]="sense.year">
</div>
...
</form>
`,
inputs: [
"sense",
"ranks",
"fields"
]
})
export class WordSense {
// model being edited
public sense: ISenseViewModel;
// lookup data
public ranks: IPair<number>[];
public fields: IFieldModel[];
public field: IFieldModel;
// form
public senseForm: ControlGroup;
public definitionCtl: Control;
public yearCtl: Control;
public rankCtl: Control;
public fieldsCtl: Control;
constructor(private formBuilder: FormBuilder) {
this.senseForm = this.formBuilder.group({
"definition": ["", Validators.required],
"year": [0, Validators.compose([Validators.required, YearValidator.isValidYear])],
"rank": [{value: 2, label: "media"}, Validators.required],
"fields": [""]
});
this.definitionCtl = <Control> this.senseForm.controls["definition"];
this.yearCtl = <Control> this.senseForm.controls["year"];
this.rankCtl = <Control> this.senseForm.controls["rank"];
this.fieldsCtl = <Control> this.senseForm.controls["fields"];
}
// ...
}
要获得更易读的错误,您可以将 Angular2 .min.js
文件更改为 .dev.js
文件。
这样做,你现在有以下错误:
No Directive annotation found on FormBuilder
实际上,问题在于您将 FORM_PROVIDERS
设置到组件的 directives
属性中。因此它尝试使用提供程序作为指令,但它们不是...
@Component({
selector: "word",
directives: [FORM_DIRECTIVES, FORM_PROVIDERS, WordSense], <-----
templateUrl: `
<div>
(...)
`
})
export class ...
删除它应该可以解决您的问题:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES, WordSense], <-----
templateUrl: `
<div>
(...)
`
})
export class ...
另一个问题是您使用 templateUrl
而不是 template
作为 Word
组件:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES,WordSense],
templateUrl: ` <----------
`
(...)
你应该改用这个:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES,WordSense],
template: ` <----------
`
(...)
这是重构后的 plunkr:http://plnkr.co/edit/x0d5oiW1J9C2JrJG8NdT?p=preview.
希望对你有帮助,
蒂埃里
我正在尝试在 Angular2(Plunker 中带有 TS 的 beta 0)中实现一个具有 2 个嵌套表单的场景,每个表单由一个组件表示。
父组件是Word
,表示假字典中的单词。子成分是 WordSense
的,每个都代表父词的一种含义。
两个组件都使用模型驱动的表单,子表单使用 ngModel
将其模型的值绑定到表单控件。这样,父组件可以轻松地将其词义向下传递给子组件,剩下的由双向绑定完成。
两种形式都附加了简单的自定义验证器。除其他事项外,我不仅想在单词形式无效时禁用提交按钮,而且在其任何含义无效时也想禁用提交按钮。为此,我在正在编辑的模型中添加了一个isValid
属性,并在代码中观察形式的变化:每当发生变化时,我检查形式的valid
[=38] =] 并相应地设置模型的 属性。然后,我可以轻松地在视图和代码中的父组件级别添加一个检查,这样我就可以 post 只有当两种形式都正常时。
为了支持自定义验证和附加逻辑,我将初始代码从基于模板的表单切换为基于模型的表单;然而,一旦我启动重构代码,我就会收到几个 No Directive annotation found 错误,我不确定它们的含义。
可能我遗漏了一些明显的东西,但我是这里的新手。任何人都可以提出建议吗?你可以在这个 plunker 找到一个 repro:http://plnkr.co/edit/v9Dj5j5opJmonxEeotcR。这是它的一些基本代码:
a) 父组件:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES, FORM_PROVIDERS, WordSense],
templateUrl: `
<div>
<form [ngFormModel]="wordForm"
(ngSubmit)="onSubmit(wordForm.value)"
role="form">
<div class="form-group"
[class.has-error]="!lemma.valid && lemma.touched">
<label for="lemma">lemma</label>
<input type="text" id="lemma"
maxlength="100" required spellcheck="false"
class="form-control"
placeholder="lemma"
[ngFormControl]="wordForm.controls['lemma']">
<div *ngIf="lemma.hasError('required') && lemma.touched"
class="text-danger small">lemma required</div>
<div *ngIf="lemma.hasError('lemmaValidator') && lemma.touched"
class="text-danger small">invalid lemma</div>
</div>
...
<div class="form-group">
<table class="table table-bordered">
<tbody>
<tr *ngFor="#s of senses">
<td>
<word-sense [sense]="s" [ranks]="ranks" [fields]="fields"></word-sense>
</td>
</tr>
</tbody>
</table>
</div>
...
<button type="submit"
[ngClass]="{disabled: !wordForm.valid}"
class="btn btn-primary btn-sm">save</button>
</form>
</div>
`,
inputs: [
"word"
]
})
export class Word {
private _word: IWordModel;
public set word(value: IWordModel) {
this._word = value;
this.setFormValues();
}
public get word() {
return this._word;
}
// ...
// form
public wordForm: ControlGroup;
public lemma: Control;
public language: Control;
public class: Control;
public ranks: IPair<number>[];
public senses: ISenseViewModel[];
public fields: IFieldModel[];
constructor(private formBuilder:FormBuilder) {
// ...
this.senses = [
this.createSense()
];
// ...
// build the form
this.wordForm = this.formBuilder.group({
"lemma": ["", Validators.compose([Validators.required, LemmaValidator.isValidLemma])],
"language": ["eng", Validators.required],
"class": ["s.", Validators.required],
});
this.lemma = <Control> this.wordForm.controls["lemma"];
this.language = <Control> this.wordForm.controls["language"];
this.class = <Control> this.wordForm.controls["class"];
// ...
}
}
b) 子组件:
@Component({
selector: "word-sense",
directives: [FORM_DIRECTIVES],
template: `
<form class="form-inline" role="form" [ngFormModel]="senseForm">
<div class="form-group"
[class.has-error]="!definitionCtl.valid">
<input type="text"
class="form-control"
placeholder="definition"
[ngFormControl]="definitionCtl"
[(ngModel)]="sense.definition">
</div>
<div class="form-group"
[class.has-error]="!yearCtl.valid">
<input type="number"
class="form-control"
placeholder="date"
[ngFormControl]="yearCtl"
[(ngModel)]="sense.year">
</div>
...
</form>
`,
inputs: [
"sense",
"ranks",
"fields"
]
})
export class WordSense {
// model being edited
public sense: ISenseViewModel;
// lookup data
public ranks: IPair<number>[];
public fields: IFieldModel[];
public field: IFieldModel;
// form
public senseForm: ControlGroup;
public definitionCtl: Control;
public yearCtl: Control;
public rankCtl: Control;
public fieldsCtl: Control;
constructor(private formBuilder: FormBuilder) {
this.senseForm = this.formBuilder.group({
"definition": ["", Validators.required],
"year": [0, Validators.compose([Validators.required, YearValidator.isValidYear])],
"rank": [{value: 2, label: "media"}, Validators.required],
"fields": [""]
});
this.definitionCtl = <Control> this.senseForm.controls["definition"];
this.yearCtl = <Control> this.senseForm.controls["year"];
this.rankCtl = <Control> this.senseForm.controls["rank"];
this.fieldsCtl = <Control> this.senseForm.controls["fields"];
}
// ...
}
要获得更易读的错误,您可以将 Angular2 .min.js
文件更改为 .dev.js
文件。
这样做,你现在有以下错误:
No Directive annotation found on FormBuilder
实际上,问题在于您将 FORM_PROVIDERS
设置到组件的 directives
属性中。因此它尝试使用提供程序作为指令,但它们不是...
@Component({
selector: "word",
directives: [FORM_DIRECTIVES, FORM_PROVIDERS, WordSense], <-----
templateUrl: `
<div>
(...)
`
})
export class ...
删除它应该可以解决您的问题:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES, WordSense], <-----
templateUrl: `
<div>
(...)
`
})
export class ...
另一个问题是您使用 templateUrl
而不是 template
作为 Word
组件:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES,WordSense],
templateUrl: ` <----------
`
(...)
你应该改用这个:
@Component({
selector: "word",
directives: [FORM_DIRECTIVES,WordSense],
template: ` <----------
`
(...)
这是重构后的 plunkr:http://plnkr.co/edit/x0d5oiW1J9C2JrJG8NdT?p=preview.
希望对你有帮助, 蒂埃里