Search code examples
htmlangularangular-forms

Grab values from dynamic form content angular


I am working with a back-end API that I query for flight data, which I then loop over and display the data. My template code looks like this...

<form novalidate #form="ngForm">
    <div class="flight-table">
      <header fxLayout="row" fxLayoutAlign="space-between">
        <div fxFlex="25">Flights</div>
          <div fxFlex="17">Class</div>
          <div fxFlex="18">Price (AED)</div>
          <div fxFlex="15">PAX</div>
        <div fxFlex="25">Total</div>
      </header>

      <main>
        <div class="flights-body--row" fxLayout="row" fxLayoutAlign="space-between" *ngFor="let flight of data.flights; let i = index">
          <p fxFlex="25">{{flight['flightName']}}</p>
          <p fxFlex="17">{{flight['flightClass']}}</p>
          <p fxFlex="18">{{flight['flightPrice'] | number}}</p>
          <p fxFlex="15" fxLayout="column">
            <mat-select placeholder="Ticket(s)">
              <mat-option *ngFor="let pax of counter" [value]="pax">{{ pax }} </mat-option>
              </mat-select>
           </p>
           <p fxFlex="25">AED 0</p>
         </div>

         <div class="flights-body--row" fxLayout="row" fxLayoutAlign="space-between">
           <p class="capitalize" fxFlex="75"><strong>Total transport</strong></p>
           <p class="center-text" fxFlex="25"><strong>AED 0</strong></p>
         </div>
      </main>
    </div>
</form>

Flights table

When a user selects a number of tickets from the PAX dropdown, I'd like to update both the row total and the total transport. I'd need to be able to grab the value of the PAX select box and multiply it by flight['flightPrice'] but I can't quite figure out how to, because I can't know how many items are in the flights array. What you see is just one row but there could be more.

Also, I'd like to capture the value for each row. I've thought about adding a hidden form field for each row or sth like that. I'm not quite sure how to deal with this. Is there some better way?

Update

I'm struggling with how to capture the selected rows. I've tried adding a hidden input field in each row like so..

<input 
  matInput 
  [name]="flight['flightName']-flight['flightClass']-pax-flight['flightPrice']" 
  [value]="total"
  ngModel> 

That doesn't seem to work. The goal is to have each selected row stored in an object like so... The dashes separate the flightName, flightClass, pax and flightPrice with the value being the total (pax * flightPrice) and pax the number of passengers.

flights: {
  lufthansa-business-2-60: 120,
  flyEmirates-first-3-50: 150
}

How should I go about it?


Solution

  • I would recommend that you create a dedicated component for your basket items. You then have an isolated scope you can work with and i.e. use [(ngModel)] to set the selected number of pax on your component (for which you have to import the FormsModule in your app/feature module). You can then reuse that value to compute the total price for a flight.

    To get the total for the entire transport (sum over all basket items), you'd also need to pass data between components. A BasketService that keeps track of your basket items can help with that. I created a little stackblitz to demonstrate how to do this: https://stackblitz.com/edit/angular-tpenou

    The relevant parts are:

    export class BasketItemComponent implements OnDestroy {
    
      @Input() flight: Flight;
      paxOptions = [0, 1, 2, 3, 4];
      pax = 0;
    
      constructor(private  basketService: BasketService) {
        this.basketService.registerBasketItemComponent(this);
      }
    
      ngOnDestroy(): void {
        this.basketService.removeBasketItemComponent(this);
      }
    
      get total() {
        return this.flight.flightPrice * this.pax;
      }
    
    }
    

    And the template:

    <div class="flights-body--row" fxLayout="row" fxLayoutAlign="space-between" >
        <p fxFlex="25">{{flight['flightName']}}</p>
        <p fxFlex="17">{{flight['flightClass']}}</p>
        <p fxFlex="18">{{flight['flightPrice'] | number}}</p>
        <p fxFlex="15" fxLayout="column">
          <mat-select placeholder="Ticket(s)" [(ngModel)]="pax">
            <mat-option *ngFor="let pax of paxOptions" [value]="pax">{{ pax }} </mat-option>
            </mat-select>
         </p>
         <p fxFlex="25">AED {{ total }}</p>
    </div>
    

    And the BasketService:

    export class BasketService {
    
      basketItems: BasketItemComponent[] = [];
    
      constructor() { }
    
      get total() {
        return this.basketItems.reduce( (a, b) => a + b.total, 0);
      }
    
      registerBasketItemComponent(c: BasketItemComponent) {
        this.basketItems.push(c);
      }
    
      removeBasketItemComponent(c: BasketItemComponent) {
        this.basketItems.splice(this.basketItems.indexOf(c), 1);
      }
    }
    

    You would also have to alter your for loop to make use of your newly created custom component:

    <form novalidate #form="ngForm">
      <div class="flight-table">
        <header fxLayout="row" fxLayoutAlign="space-between">
          <div fxFlex="25">Flights</div>
            <div fxFlex="17">Class</div>
            <div fxFlex="18">Price (AED)</div>
            <div fxFlex="15">PAX</div>
          <div fxFlex="25">Total</div>
        </header>
    
        <main>
          <app-basket-item [flight]="flight" *ngFor="let flight of data.flights; let i = index">
    
          </app-basket-item>
    
           <div class="flights-body--row" fxLayout="row" fxLayoutAlign="space-between">
             <p class="capitalize" fxFlex="75"><strong>Total transport</strong></p>
             <p class="center-text" fxFlex="25"><strong>AED {{transportTotal}}</strong></p>
           </div>
        </main>
      </div>
    </form>