Могу ли я использовать реактивные формы в angular с полем ввода, которое обрабатывается отдельно при сохранении и заполнении, чем остальные поля ввода?

В настоящее время я использую форму, управляемую шаблоном, и [(ngModel)]

У меня есть модель продукта, показанная ниже.


    export interface Product {

    title: string;
    price: number;
    category: string;
    imageUrl: string;
    key: string;
    description: string;
    
}

Я также храню в отдельном месте в базе данных, сколько единиц продукта доступно. В настоящее время хранение происходит двумя отдельными службами.

при сохранении/обновлении/создании я использую ту же форму.

ниже я предоставляю компонент productForm..

import { QuantityAvailableService } from './../../quantity-available.service';
import { Router, ActivatedRoute } from '@angular/router';
import { ProductService } from './../../product.service';
import { CategoryService } from './../../category.service';
import { Component, OnInit } from '@angular/core';
import { take } from 'rxjs/operators';
import { Observable } from 'rxjs';




@Component({
  selector: 'app-product-form',
  templateUrl: './product-form.component.html',
  styleUrls: ['./product-form.component.css']
})

export class ProductFormComponent implements OnInit {

  
  categories$: Observable<{}>;
  product: any  = {};
  stockQuantity: any = {};
  id;


  constructor(
    private categoryService: CategoryService,
    private route: ActivatedRoute,
    private productService: ProductService,
    private quantityAvailableService: QuantityAvailableService,
    private router: Router
    ) {
    
    this.categories$ = this.categoryService.getAll().snapshotChanges();

    this.id = this.route.snapshot.paramMap.get('id');
    if (this.id) {
      this.quantityAvailableService.get(this.id).subscribe(data => this.stockQuantity = data);
      this.productService.get(this.id).pipe(take(1)).subscribe(p => {
      return this.product = p;
     });
  }}

  ngOnInit() { }


  save(product) {
    if (this.id) {

      this.productService.update(this.id, product);
      this.quantityAvailableService.update(this.id, this.stockQuantity);

     } else {
       this.productService.create(product);
       this.quantityAvailableService.create(this.id, this.stockQuantity);
      }

    this.router.navigate(['/admin/products']);
  }



  delete() {
    if (!confirm('Are you sure you want to delete this product?')) {  return; }

    this.productService.delete(this.id);
    this.quantityAvailableService.remove(this.id);
    this.router.navigate(['/admin/products']);

  }
}

   <div class="row">
  <div class="col-md-6">
      <form #f="ngForm" (ngSubmit)="save(f.value)">
          
        <div class="form-group">
            <label for="title">Title</label>
            <input #title="ngModel" [(ngModel)]="product.title" name="title" id ="title" type="text" 
                class="form-control" required>
            <div class="alert alert-danger" *ngIf="title.touched && title.invalid">
              Title is required.
            </div>
         </div>
          
          <div class="form-group">
              <label for="price">Price</label>
              <div class="input-group-prepend">
                <span class="input-group-text">$</span>
              <input #price="ngModel" [(ngModel)]="product.price" name="price" id ="price" type="number" 
                 class="form-control" required [min]="0"> 
              </div>
              <div class="alert alert-danger" *ngIf="price.touched && price.invalid">
                <div *ngIf="price.errors.required">Price is required.</div>
                <div *ngIf="price.errors.min">Price should be 0 or higher.</div>
              </div>
          </div>
         
          <div class="form-group">
            <label for="quantityAvailable">Quantity available</label>
            <div class="input-group-prepend">
            <input #quantityAvailable="ngModel" [(ngModel)]="stockQuantity" name="quantityAvailable" id 
               ="quantityAvailable" type="number" class="form-control" required [min]="0"> 
            </div>
           <div class="alert alert-danger" *ngIf="quantityAvailable.touched && 
                 quantityAvailable.invalid">
              <div *ngIf="quantityAvailable.errors.required">Quantity available is required.</div>
              <div *ngIf="quantityAvailable.errors.min">Quantity available should be 0 or higher.</div>
            </div>
          </div>

          <div class="form-group">
              <label for="category">Category</label>
              <select #category="ngModel" [(ngModel)]="product.category" name="category" id ="category" 
                 class="form-control"  required>
                <option value=""></option>
                <option *ngFor="let c of categories$ | async" [value]="c.payload.val().name">
                  {{ c.payload.val().name }}
                </option>
                </select>
              <div class="alert alert-danger" *ngIf="category.touched && category.invalid">
                Category is required.
              </div>
          </div>

          <div class="form-group">
              <label for="description">Description</label>
              <textarea rows="7" #description="ngModel" [(ngModel)]="product.description" 
               name="description" id ="description" class="form-control rounded-0" required></textarea>
                <div class="alert alert-danger" *ngIf="description.touched && description.invalid">
                description is required.
              </div>
          </div>

          <div class="form-group">
           <label for="imageUrl">Image URL</label>
           <input #imageUrl="ngModel" [(ngModel)]="product.imageUrl" name="imageUrl" id ="image" 
               type="text" class="form-control" required url>
              <div class="alert alert-danger" *ngIf="imageUrl.touched && imageUrl.invalid">
              <div *ngIf="imageUrl.errors.required">ImageUrl is required.</div>
              <div *ngIf="imageUrl.errors.url">Please enter a valid URL</div>
            </div>
          </div>
    </form>
  </div>
  <div class="col-md-6">
      <product-card [product]="product" [showActions]="false"></product-card>
  </div>
</div>

Я хочу создать поле ввода, в котором будут храниться URL-адреса в массиве и динамически обновляться, то есть проверять действительность, хранить в массиве (локально, а не в базе данных) и отображать значение в поле ввода в списке, и это перед отправкой форму, отправив ее в базу данных.

.. поэтому я рассматриваю возможность превращения формы в реактивную форму, чтобы я мог динамически создавать свой массив.

..моя проблема..

у меня есть поле ввода, которое не связано с моей моделью продукта, и мне нужно заполнить его при обновлении формы, мне также нужно, чтобы оно требовалось и реагировало с помощью функции отправки формы (отключить кнопку отправки), наконец, мне нужно хранить его в другом месте, чем продукт через отдельный сервис.

возможно ли это при реактивных формах? любые предложения приветствуются..

у меня около года опыта в программировании, надеюсь, я предоставляю достаточно информации..


person Cabamarou Meh Meh Meh    schedule 14.12.2020    source источник
comment
Я решил отказаться от этой практики, потому что она вводит много ненужного кода и работает внутри модели проекта. я также обнаружил ошибку в подходе, который я пытался реализовать. Я не смог заставить работать подход Eliseo, однако я считаю, что лучшим подходом к этому вопросу является предложение @djangofan использовать 2 объекта формы в вашей форме, переданных как FormArray .. это выглядело бы более чистым кодом.   -  person Cabamarou Meh Meh Meh    schedule 18.12.2020


Ответы (2)


Да, вы можете это сделать.

Допустим, у меня есть форма, представляющая объект Song, И у меня есть следующие 2 метода для моего объекта SongHandlerService.

updateSong(index: number, newSong: FormGroup) {
  const song = this.convertFormToSong(newSong.value);
  ...
{

На моей странице формы кнопка отправки вызывает метод на странице формы, который, в свою очередь, вызывает этот метод updateSong.

Теперь форму можно разбить на необработанный тип, как вы видите в аргументах этого метода:

private convertFormToSong(rawSong: {owner: string, title: string, composer: string,
    key: string, data: string, meta: string, published: boolean}): Song {
    return new Song(rawSong.owner, rawSong.title, rawSong.composer,
      rawSong.key, JSON.parse(rawSong.data), JSON.parse(rawSong.meta), rawSong.published);
  }

Как видите, в методе такого типа при получении объекта формы как необработанного типа мы можем реструктурировать данные формы любым удобным для нас способом после того, как данные будут переданы из формы.

ПРИМЕЧАНИЕ. Другой способ сделать это - иметь 2 объекта формы в вашей форме, переданных как FormArray. Я еще не пробовал это сам.

person djangofan    schedule 14.12.2020
comment
я вижу, вы предлагаете слой перевода между формой и остальной частью приложения, поэтому, возможно, ему нужна еще одна функция, такая как convertFormToSong(), когда вы вызываете форму для обновления уже существующей песни, и вам нужно заполнить поля ввода. Итак, если я хочу извлечь / импортировать значение из песни, чтобы направить его в другое место, или мне нужно передать значение из другого места в форму, нужно ли мне извлекать или передавать соответствующие необработанные данные и метаданные? Если да, то как вы их извлекаете? - person Cabamarou Meh Meh Meh; 14.12.2020
comment
Да, у вас есть хорошая мысль. Я думаю, вы это понимаете (вам понадобится метод convertSongToFormLoadable). Мое решение может быть не лучшим для вашего случая, но оно работает для меня, поскольку дополнительное поле в моей форме представляет собой объект JSON, который загружается в поле формы как строковый объект JSON. Итак, если у вас более сложная ситуация, посмотрите другие ответы здесь (или FormArray). - person djangofan; 15.12.2020

Я думаю, что вы можете получить другой подход. Ну, у вас есть две службы, но вы можете иметь уникальный объект и уникальную группу форм.

Это только идея, вы можете, например.

this.id = this.route.snapshot.paramMap.get('id');
if (this.id) {
  fokJoin(
         this.productService.get(this.id),
         this.quantityAvailableService.get(this.id)
  ).subscribe(([product,quantity]=>{
    product.quantity=quantity; //<--add a new property to Quantity
    this.form=this.getFormGroup(product)
  )
}
else
    this.form=this.getFormGroup()

getFormGroup(product:any=null)
{
    product=product || {} as Product
    product.quantity=product.quantity || 0
    return new FormGroup({
       categoryService:new FormControl(product.categoryService,Validators.required)
       ...
       quantity:new FromControl(product.quantity)
    })
}

В сохранении

save(form)
{
    if (form.valid)
    {
        const quantity=form.value.quantity;
        const product={...form.value};
        delete product.quantity;
        if (product.id) {
           this.productService.update(this.id, product);
           this.quantityAvailableService.update(this.id, this.stockQuantity);
       } else {
          this.productService.create(product);
          this.quantityAvailableService.create(this.id, this.stockQuantity);
       }
}

Обновите, чтобы сделать его более подходящим, мы можем расширить интерфейс

export interface ProductExtend extends Product
{
  quantity:number;
}

или добавьте необязательное свойство к нашему продукту

export interface Product {
    title: string;
    ....
    quantity?:number;
}

Мы также можем использовать map для forkJoin, чтобы вернуть уникальный объект.

fokJoin([
         this.productService.get(this.id),
         this.quantityAvailableService.get(this.id)]
  ).pipe(map([product,quantity]:[Product:number])=>{
    product.quantity=quantity; //<--add a new property to Quantity
    return product;
  )

or

fokJoin([
         this.productService.get(this.id),
         this.quantityAvailableService.get(this.id)]
  ).pipe(map([product,quantity]:[Product:number])=>{
    return productExtend:ProductExtend={...product,quantity:quantity}
  )

ПРИМЕЧАНИЕ. Всегда есть чувак, то есть если я делаю forkJoin в компоненте или создаю новую функцию в службе

ПРИМЕЧАНИЕ 2: для создания formGroup мне нравится функция getFormGroup, которая подает данные

getForm(data:Product=null):FormGroup
{
    data=data || {} as Product
    return new FormGroup({
      title: new FormControl(data.title,Validatros.required),
      price:new FormControl(data.price)
      ...
    });
}
person Eliseo    schedule 14.12.2020
comment
fokJoin() устарел и предлагает вместо этого использовать версию, которая принимает массив Observables. (больше читать :)) - person Cabamarou Meh Meh Meh; 15.12.2020
comment
после некоторого исследования вы можете использовать forkJoin, передавая массив наблюдаемых. поэтому я сделал это: forkJoin ([this.productService.get(this.id).pipe(take(1)), this.quantityAvailableService.get(this.id) ]) .subscribe(([product, количество]) = › {product.quantityAvailable = количество;} ); но компилятор жалуется, потому что он знает, что модель продукта не использует доступное количество - person Cabamarou Meh Meh Meh; 15.12.2020
comment
кажется, что вы можете выбрать элемент управления и привязать значение непосредственно к нему. вот что я сделал: this.quantityAvailableService.get(this.id).subscribe(data => this.form.controls["quantityAvailable"].patchValue(data)) ; - person Cabamarou Meh Meh Meh; 16.12.2020
comment
Я обновил ответ, если вы хотите расширить интерфейс или создать необязательное свойство. Я вообще предпочитаю создавать функцию, которая возвращает formControl - person Eliseo; 17.12.2020