Заполнение раскрывающегося списка в динамической форме в Angular 2

У меня такой сценарий:

Это мое определение поля

 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 });;
}
}

Не могли бы вы сказать мне, в чем именно проблема, я запутался

Abel Valdez 17.06.2018 02:53

@AbelValdez Я не могу заполнить раскрывающийся список значениями для авторов и категорий, я обновлю вопрос тем, что я пробовал с тех пор, как опубликовал

Frey_ja 17.06.2018 20:36

@AbelValdez Обновил, последняя часть :)

Frey_ja 17.06.2018 21:00

Почему вы назначаете массив в параметре var внутри foreach?

Abel Valdez 17.06.2018 21:14

Я пытаюсь установить ключ раскрывающегося списка с помощью authorId и значение раскрывающегося списка с помощью authorName. authorDropdown - это тип DropdownField. Я действительно не знаю, что еще попробовать ... все равно вылетает здесь "авторы: this.authorDropdown" ..

Frey_ja 17.06.2018 21:37

Как я могу установить свойство авторов из книги в результат authorService.getAuthors () и свойство категорий в результат categoryService.getCategories (), учитывая, что я использую динамические формы?

Frey_ja 17.06.2018 21:39

Измените его с помощью ... options.push ({key ...}), чтобы указать своих авторов.

Abel Valdez 17.06.2018 21:48

Но есть ли возможность сделать authorService.getAuthors (), categoryService.getCategories () и bookService.getBook (), используя только одну подписку? потому что даже если мне удастся заполнить authorDropdown и categoryDropdown, здесь не удастся: (this.book = {bookId: 0, title: '', авторы: this.authorDropdown, Categories: this.categoryDropdown};)

Frey_ja 17.06.2018 21:57

И как мне заменить эту функцию: (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"

Frey_ja 17.06.2018 22:03

Это не имеет смысла, потому что вам напрямую назначается массив, который вы назначаете только последнему элементу, а не всему массиву

Abel Valdez 17.06.2018 22:06

И как правильно это сделать? :)

Frey_ja 18.06.2018 19:30

Я добавил новый ответ.

Abel Valdez 18.06.2018 19:39
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
0
12
474
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это правильный способ сделать это, ваш код просто добавляет последний элемент, когда вы повторяете массив авторов, теперь вы собираетесь добавить каждый элемент от авторов здесь 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;»

Frey_ja 18.06.2018 20:24

Не могли бы вы опубликовать, где вы определяете this.authorDropdown эту переменную

Abel Valdez 18.06.2018 20:25

Выложил ниже "Обновление 2" :)

Frey_ja 18.06.2018 20:36

поменять authorDropdown: DropdownField; на authorDropdown= new DropdownField();

Abel Valdez 18.06.2018 20:39

Другие вопросы по теме