我試圖首次使用name
和[(ngModel)]
模板驅動的窗體語法,在使用我第一次使用的controlValueAccessor
的自定義控件上。Angular 2 form.value屬性未定義
當我輸入一些詞語在我的<input>
然後登錄我form.value
到控制檯,我看到我添加表單域的名稱,但它仍然是不確定的:
Object {keywords: undefined}
如果我以編程方式設置的值對於result.keywords,然後當我將form.value記錄到控制檯時,關鍵字屬性被填充。從模型到form.value的綁定正在工作。從視圖(html輸入控件)到模型的綁定不起作用。
ngOnInit() {
this.result = new Result();
this.result.keywords = ["aaa"]; <----works
}
以上將在控制檯中顯示[「aaa」],但它不會在視圖中顯示任何內容。 如何正確獲取表單的關鍵字屬性以填充?
我的代碼:
我的形式:
<form class="text-uppercase" (ngSubmit)="onSubmit(findForm.value, findForm.valid)" #findForm="ngForm">
<vepo-input
[placeholder]='"keywords (optional)"'
[id]='"keywordsInput"'
name="keywords"
[(ngModel)]="result.keywords">
</vepo-input>
</form>
輸入component.ts:
import { Component, ViewChild, ElementRef, Input, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
const noop =() => {
};
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputComponent),
multi: true
};
@Component({
selector: 'vepo-input',
templateUrl: 'app/shared/subcomponents/input.component.html',
styleUrls: ['app/shared/subcomponents/input.component.css'],
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class InputComponent implements ControlValueAccessor {
@Input() private placeholder: string;
@Input() private id: string;
//The internal data model
private innerValue: any = '';
//Placeholders for the callbacks which are later providesd
//by the Control Value Accessor
private onTouchedCallback:() => void = noop;
private onChangeCallback: (_: any) => void = noop;
//get accessor
get value(): any {
return this.innerValue;
};
//set accessor including call the onchange callback
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
//Set touched on blur
onBlur() {
this.onTouchedCallback();
}
//From ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
//From ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
//From ControlValueAccessor interface
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
input.component.html:
<input class="form-item-input"
placeholder={{placeholder}}
id={{id}} />
<label attr.for="{{id}}"
class="form-item-right-icon input-icon">
</label>
我的表單實際上比我發佈的表單大很多,但我不想用無關代碼重載所有人。爲了完整起見,這裏是全form.ts文件:
import {
Component,
ViewChild,
ElementRef,
EventEmitter,
Output,
OnInit
} from '@angular/core';
import {
FormGroup,
FormBuilder,
Validators,
ControlValueAccessor
} from '@angular/forms';
import { ResultService } from '../../../services/result.service';
import { Result } from '../../../models/all-models';
import { HighlightDirective } from '../../../directives/highlight.directive';
import { DistanceUnitsComponent } from './distance-units.component';
import { MultiselectComponent } from './multiselect-find-category.component';
import { MultiselectFindMealTypeComponent } from
'./multiselect-find-meal-type.component';
import { AreaComponent } from './area-picker.component';
import { NumberPickerComponent } from './number-picker.component';
import { InputComponent } from '../../../shared/subcomponents/input.component';
@Component({
selector: 'find-form',
templateUrl: 'app/find-page/subcomponents/find-page/find-form.component.html',
styleUrls: ['app/find-page/subcomponents/find-page/find-form.component.css'],
providers: [ResultService]
})
export class FindFormComponent implements OnInit {
@ViewChild('multiselectFindCategory')
private multiselectFindCategory: MultiselectComponent;
@ViewChild('multiselectFindMealType')
private multiselectFindMealType: MultiselectFindMealTypeComponent;
@ViewChild('distanceUnits') private distanceUnits: DistanceUnitsComponent;
@ViewChild('numberPicker') private numberPicker: NumberPickerComponent;
@ViewChild('areaInput') private areaInput: AreaComponent;
@ViewChild('keywordsInput') private keywordsInput: InputComponent;
@Output() private onResultsRecieved:
EventEmitter<Object> = new EventEmitter<Object>();
@Output() private onSubmitted: EventEmitter<boolean> =
new EventEmitter<boolean>();
private categoryError: string = 'hidden';
private mealTypeError: string = 'hidden';
private areaError: string = 'hidden';
private findForm: FormGroup;
private submitted: boolean = false;
private result: Result;
private displayMealCategories: boolean = false;
private mealSelected: boolean = false;
private place: google.maps.Place;
constructor(private resultService: ResultService,
private formBuilder: FormBuilder,
el: ElementRef) { }
ngOnInit() {
this.result = new Result();
}
private setCategoryErrorVisibility(
multiselectFindCategory: MultiselectComponent
): void {
if (multiselectFindCategory.selectedCategories.length < 1 &&
!multiselectFindCategory.allSelected &&
this.submitted) {
this.categoryError = 'visible';
} else {
this.categoryError = 'hidden';
}
}
private setMealTypeErrorVisibility(
multiselectFindMealType: MultiselectFindMealTypeComponent
): void {
if (multiselectFindMealType) {
if (multiselectFindMealType.selectedCategories.length < 1 &&
!multiselectFindMealType.allSelected &&
this.submitted) {
this.mealTypeError = 'visible';
} else {
this.mealTypeError = 'hidden';
}
}
}
private setAreaErrorVisibility(): void {
if (this.areaInput.areaInput.nativeElement.value) {
if (!this.areaInput.address) {
this.areaError = 'visible';
this.areaInput.areaInput.nativeElement.setCustomValidity("Please select from dropdown or leave blank.");
} else {
this.areaError = 'hidden';
this.areaInput.areaInput.nativeElement.setCustomValidity("");
}
} else {
this.areaError = 'hidden';
this.areaInput.areaInput.nativeElement.setCustomValidity("");
}
}
private onCategoriesChanged(): void {
this.setCategoryErrorVisibility(this.multiselectFindCategory);
this.mealSelected = this.multiselectFindCategory.mealSelected;
if (!this.mealSelected) {
this.mealTypeError = 'hidden';
}
}
private onMealTypesChanged(): void {
this.setMealTypeErrorVisibility(this.multiselectFindMealType);
}
private onAreaChanged(areaEntered: any): void {
this.setStateOfDistanceControls(areaEntered.areaEntered);
this.areaError = "hidden";
this.areaInput.areaInput.nativeElement.setCustomValidity("");
if (areaEntered.place) {
this.place = areaEntered.place;
}
}
private setStateOfDistanceControls(areaEntered: any): void {
if (areaEntered.areaEntered) {
this.distanceUnits.isEnabled = true;
this.numberPicker.isEnabled = true;
} else {
this.distanceUnits.isEnabled = false;
this.numberPicker.isEnabled = false;
}
this.distanceUnits.setImage();
}
private getResults(): void {
var results: Result[] = [];
results = this.resultService.getResults();
if (results) {
this.onResultsRecieved.emit({
recieved: true,
results: results,
place: this.place
});
}
}
private onSubmit(model: any, isValid: boolean): void {
console.log(model, isValid);
// this.submitted = true;
// this.setCategoryErrorVisibility(this.multiselectFindCategory);
// this.setMealTypeErrorVisibility(this.multiselectFindMealType);
// this.setAreaErrorVisibility();
// if (this.areaError === "hidden" &&
// this.categoryError === "hidden" &&
// this.mealTypeError === "hidden") {
// this.onSubmitted.emit(true);
// this.getResults();
// }
}
}