У меня такой сценарий:
Это мое определение поля
export interface FieldDefinition {
key: string,
type: string,
isId: boolean,
label: string,
required: boolean
}
Это определение динамического поля
<div *ngSwitchCase = "'string'" class = "form-group">
<label [htmlFor] = "field.key">{{ field.label }}</label>
<textarea class = "form-control" [formControlName] = "field.key" [id] = "field.key"
[readonly] = "operation == 'details' || field.isId"></textarea>
</div>
<div *ngSwitchCase = "'number'" class = "form-group">
<label [htmlFor] = "field.key">{{ field.label }}</label>
<input class = "form-control" [formControlName] = "field.key" [id] = "field.key"
[readonly] = "operation == 'details' || field.isId"
type = "number">
</div>
<div *ngSwitchCase = "'[]'" class = "form-group">
<label [htmlFor] = "field.key">{{ field.label }}</label>
<select [id] = "field.key"
[formControlName] = "field.key">
<option *ngFor = "let opt of field.options"
[value] = "opt.key">
{{opt.value}}
</option>
</select>
</div>
<div *ngIf = "form.get(field.key).hasError('required') && (submitted || form.get(field.key).touched)"
class = "alert alert-danger">
{{ field.label }} is required.
</div>
</div>
</div>
Файл ts:
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FieldDefinition } from '../field-definition';
@Component({
selector: 'fw-dynamic-field',
templateUrl: './dynamic-field.component.html',
styleUrls: ['./dynamic-field.component.css']
})
export class DynamicFieldComponent {
@Input() field: FieldDefinition;
@Input() form: FormGroup;
@Input() operation: string;
@Input() submitted: boolean;
get isValid() { return this.form.controls[this.field.key].valid; }
constructor() { }
}
Динамическая форма html
<form (ngSubmit) = "onSubmit()" [formGroup] = "form">
<div *ngFor = "let field of vmDefinition">
<fw-dynamic-field [field] = "field"
[form] = "form"
[operation] = "operation"
[submitted] = "submitted">
</fw-dynamic-field>
</div>
<div *ngIf = "errorMessage " class = "alert alert-danger">
{{ errorMessage }}
</div>
<div *ngIf = "status != 'waiting'">
<div *ngIf = "operation === 'details'">
<button type = "button" class = "btn" (click) = "onBack()" >Back</button>
<button type = "button" class = "btn btn-primary" (click) = "onEdit()" >Edit</button>
</div>
<div *ngIf = "operation === 'edit'">
<button type = "button" class = "btn" (click) = "onCancel()" >Cancel</button>
<button type = "button" class = "btn btn-primary" (click) = "onSave()" >Save</button>
</div>
<div *ngIf = "operation === 'create'">
<button type = "button" class = "btn" (click) = "onBack()" >Back</button>
<button type = "button" class = "btn btn-primary" (click) = "onCreate()" >Create</button>
</div>
</div>
</form>
И файл ts:
export class DynamicFormComponent implements OnChanges, OnInit {
@Input() vm: any;
@Input() vmDefinition: Array<FieldDefinition>;
@Input() operation: string;
@Input() errorMessage: string;
@Output() update: EventEmitter<any> = new EventEmitter();
@Output() create: EventEmitter<any> = new EventEmitter();
form: FormGroup;
status: string;
submitted = false;
vmCopy: any;
constructor(private router: Router,
private route: ActivatedRoute,
private location: Location) { }
clearForm() {
const group = {};
this.vmCopy = Object.assign({}, this.vm);
this.vmDefinition.forEach(field => {
group[field.key] = field.required ? new FormControl(this.vmCopy[field.key], Validators.required)
: new FormControl(this.vmCopy[field.key]);
});
this.form = new FormGroup(group);
}
ngOnChanges(changes: SimpleChanges) {
if (changes['errorMessage'].currentValue && this.status === 'waiting') {
this.status = '';
}
}
ngOnInit() {
this.clearForm();
this.route.params.subscribe(params => {
this.operation = params['operation'];
this.clearForm();
});
}
Я пытаюсь использовать эту динамическую форму на странице с описанием книги, но у меня возникают проблемы с заполнением раскрывающегося списка
export class BookDetailComponent implements OnInit {
book: Book;
books: IBook[];
authors: IAuthor[];
categories: ICategory[];
errorMessage: string;
bookDefinition: Array<FieldDefinition> = [
{
key: 'bookId',
type: 'number',
isId: true,
label: 'BookId',
required: true
},
{
key: 'title',
type: 'string',
isId: false,
label: 'Title',
required: true
},
{
key: 'authors',
type: '[]',
isId: false,
label: 'Authors',
required: false
},
{
key: 'categories',
type: '[]',
isId: false,
label: 'Categories',
required: false
}
];
operation: string;
constructor(private route: ActivatedRoute,
private router: Router,
private bookService: BookService,
private authorService: AuthorService,
private categoryService: CategoryService) { }
ngOnInit(): void {
this.operation = this.route.snapshot.params['operation'];
this.authorService.getAuthors()
.subscribe((authors: IAuthor[]) => { this.authors = authors });
this.categoryService.getCategories()
.subscribe((categories: ICategory[]) => { this.categories = categories });
if (this.operation === 'create') {
this.book = { bookId: 0, title: '', authors: this.authors, categories:
this.categories };
} else {
this.bookService.getBook(this.route.snapshot.params['id'])
.subscribe((book: Book) => { this.book = book });
}
}
А в book-detail-component.html у меня есть это
<h3>Book</h3>
<fw-dynamic-form *ngIf='book' [vm] = "book" [vmDefinition] = "bookDefinition"
[operation] = "operation"
[errorMessage] = "errorMessage"
(update) = "updateBook($event)"
(create) = "createBook($event)">
Он отлично работает с другими полями, но у меня проблемы с выпадающими списками для авторов и категорий. Прежде всего, как я могу сделать вызовы getAuthors и getCategories в одной подписке? А как потом добавить значения в раскрывающиеся списки? Я должен создать dropdownField и установить для него тип поля? Потому что у меня сейчас нет свойства options в поле .. Большое спасибо !
ОБНОВИТЬ :
Я создал это:
import { FieldDefinition } from './field-definition';
export class DropdownField extends FieldDefinition {
type: 'dropdown';
options: { key: number, value: string }[] = [];
constructor(options: {} = {}) {
super();
this.options = options['options'] || [];
}
}
И я попытался использовать его в компоненте book-detail-component, чтобы заполнить раскрывающийся список для авторов и раскрывающийся список для категорий.
ngOnInit(): void {
this.operation = this.route.snapshot.params['operation'];
this.authorService.getAuthors()
.subscribe((authors: IAuthor[]) => {
this.authors = authors,
authors.forEach(function (author) {
this.authorDropdown.options = [{ key: this.author.authorId, value: this.author.authorName }]
});
});
this.categoryService.getCategories()
.subscribe((categories: ICategory[]) => { this.categories = categories, console.info(this.categories) });
if (this.operation === 'create') {
this.book = { bookId: 0, title: '', authors: this.authorDropdown, categories: this.categories };
} else {
this.bookService.getBook(this.route.snapshot.params['id'])
.subscribe((book: Book) => { this.book = book });
}
}
По-прежнему не работает ..
Обновление 2
export class BookDetailComponent implements OnInit {
book: Book;
author: Author;
books: IBook[];
authors: IAuthor[];
categories: ICategory[];
errorMessage: string;
bookDefinition: Array<FieldDefinition> = [
{
key: 'bookId',
type: 'number',
isId: true,
label: 'BookId',
required: true
},
{
key: 'title',
type: 'string',
isId: false,
label: 'Title',
required: true
},
{
key: 'authors',
type: '[]',
isId: false,
label: 'Authors',
required: false
},
{
key: 'categories',
type: '[]',
isId: false,
label: 'Categories',
required: false
}
];
operation: string;
authorDropdown: DropdownField;
categoryDropdown: DropdownField;
constructor(private route: ActivatedRoute,
private router: Router,
private bookService: BookService,
private authorService: AuthorService,
private categoryService: CategoryService) { }
ngOnInit(): void {
this.operation = this.route.snapshot.params['operation'];
this.authorService.getAuthors()
.subscribe((authors: IAuthor[]) => {
this.authors = authors,
authors.forEach(function (author) {
this.authorDropdown.options.push({ key: this.author.authorId, value: this.author.authorName });
});
console.info(this.authorDropdown);
});
if (this.operation === 'create') {
this.book = { bookId: 0, title: '', authors: this.authorDropdown, categories: [] };
} else {
this.bookService.getBook(this.route.snapshot.params['id'])
.subscribe((book: Book) => { this.book = book });;
}
}
@AbelValdez Я не могу заполнить раскрывающийся список значениями для авторов и категорий, я обновлю вопрос тем, что я пробовал с тех пор, как опубликовал
@AbelValdez Обновил, последняя часть :)
Почему вы назначаете массив в параметре var внутри foreach?
Я пытаюсь установить ключ раскрывающегося списка с помощью authorId и значение раскрывающегося списка с помощью authorName. authorDropdown - это тип DropdownField. Я действительно не знаю, что еще попробовать ... все равно вылетает здесь "авторы: this.authorDropdown" ..
Как я могу установить свойство авторов из книги в результат authorService.getAuthors () и свойство категорий в результат categoryService.getCategories (), учитывая, что я использую динамические формы?
Измените его с помощью ... options.push ({key ...}), чтобы указать своих авторов.
Но есть ли возможность сделать authorService.getAuthors (), categoryService.getCategories () и bookService.getBook (), используя только одну подписку? потому что даже если мне удастся заполнить authorDropdown и categoryDropdown, здесь не удастся: (this.book = {bookId: 0, title: '', авторы: this.authorDropdown, Categories: this.categoryDropdown};)
И как мне заменить эту функцию: (this.authors.forEach (function (author) {this.authorDropdown.options.push ({key: this.author.authorId, value: this.author.authorName});}); ), потому что он возвращает "ERROR TypeError: Cannot read property 'authorDropdown' of undefined"
Это не имеет смысла, потому что вам напрямую назначается массив, который вы назначаете только последнему элементу, а не всему массиву
И как правильно это сделать? :)
Я добавил новый ответ.





Это правильный способ сделать это, ваш код просто добавляет последний элемент, когда вы повторяете массив авторов, теперь вы собираетесь добавить каждый элемент от авторов здесь this.authorDropdown.options с помощью push.
this.authorService.getAuthors()
.subscribe((authors: IAuthor[]) => {
this.authors = authors,
authors.forEach(function (author) {
this.authorDropdown.options
.push({ key: this.author.authorId, value: this.author.authorName });
});
});
Я пробовал сделать это вчера вечером, но он возвращает «ERROR TypeError: Cannot read property 'authorDropdown' of undefined» .. хотя это объявлено так: «authorDropdown: DropdownField;»
Не могли бы вы опубликовать, где вы определяете this.authorDropdown эту переменную
Выложил ниже "Обновление 2" :)
поменять authorDropdown: DropdownField; на authorDropdown= new DropdownField();
Не могли бы вы сказать мне, в чем именно проблема, я запутался