Calculate only selected values of Angular Material selection list

Viewed 638

I have a simple Angular Material selection list with some values inside it.

My requirement is to calculate the sum of the selected (checked) values only (and after that, apply a discount to the price, if it exceeds some amount).

I believe it must be easy, but I am new to Angular and Material components, and somehow cannot find what I want on Google or here... My code is as below:

HTML

 <mat-selection-list #options>
    <mat-list-option *ngFor="let option of optionArray">
      {{option.name}} - ${{option.price}}

    </mat-list-option>
  </mat-selection-list>

  <p>
    You have selected {{options.selectedOptions.selected.length}} items with total sum of $ {{  }}. </p>
  <p *ngIf="">
    Your discount is 10%. </p>

TS

interface Options {
  name: string;
  price: number;
}

optionArray: Options[] = [
    {
      name: 'Item 1',
      price: 4000
    },
    {
      name: 'Item 2',
      price: 8000
    },
    {
      name: 'Item 3',
      price: 3500
    },
    {
      name: 'Item 4',
      price: 500
    }
  ]

Thank you in advance!

3 Answers

Attach a model and a change event to your mat-selection-list element and then, you can use the javascript methods: map, reduce.

HTML:

<mat-selection-list #options [(ngModel)]="selectedOptions" (ngModelChange)="onSelectedOptionsChange($event)">
    <mat-list-option *ngFor="let option of optionArray">
        {{option.name}} - ${{option.price}}
    </mat-list-option>
</mat-selection-list>

<p>
    You have selected {{selectedOptions.selected.length}} items with total sum of $ {{ totalSum }}. </p>
<p *ngIf="..."> Your discount is 10%. </p>

TS controller:

onSelectedOptionsChange() {
    this.totalSum = this.selectedOptions
        .map((option: Options) => option.price)
        .reduce((priceA: number, priceB: number) => priceA + priceB)
}

Hope this helps.

There are multiple ways to achieve it. Here is another way without binding to the model property and only using template variable.

Template

<mat-selection-list #options (selectionChange)="onSelectionChange()">
  <mat-list-option *ngFor="let option of optionsArray" [value]="option">
    {{option.name}} - ${{option.price}}
  </mat-list-option>
</mat-selection-list>

<p>You have selected {{options.selectedOptions.selected.length}} items with total sum of ${{ sum }}.</p>
<p *ngIf="discount">Your discount is 10%. </p>

Controller

export class ListSelectionExample {
  @ViewChild('options') optionsSelectionList: MatSelectionList;
  optionsArray: Options[] = [
    { name: 'Item 1', price: 4000 },
    { name: 'Item 2', price: 8000 },
    { name: 'Item 3', price: 3500 },
    { name: 'Item 4', price: 50 }
  ];
  sum = 0;
  discount = false;

  onSelectionChange() {
    this.getSum();
  }

  getSum() {
    this.sum = 0;
    this.discount = false;

    this.optionsSelectionList.selectedOptions.selected
      .map(s => s.value)
      .forEach(option => this.sum += option.price);

    if (this.sum > 12500) {   // apply discount if sum > $12500
      this.sum = this.sum * 0.9;
      this.discount = true;
    }
  }
}

Working example: Stackblitz

You can just go reactive way. Grab the MatSelectionList instance via ViewChild:

@ViewChild(MatSelectionList, { static: true }) matSelectionOptionsComponentRef: MatSelectionList;

... and perform the calculations/assign the observables by listening the EventEmitter from it.


TS:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-selector',
  styleUrls: ['./app-selector.css'],
  templateUrl: './app-selector.html'
})
export class AppSelectorComponent {
  @ViewChild(MatSelectionList, { static: true })
  readonly matSelectionOptionsComponentRef: MatSelectionList;
  readonly options: readonly Options[] = [
    { name: 'Item 1', price: 4000 },
    { name: 'Item 2', price: 8000 },
    { name: 'Item 3', price: 3500 },
    { name: 'Item 4', price: 50 }
  ];
  hasDiscount$: Observable<boolean>;
  sum$: Observable<number>;

  ngOnInit(): void {
    this.sum$ = this.matSelectionOptionsComponentRef.selectionChange.pipe(
      map(({ source }) => {
        return source.selectedOptions.selected
          .map(({ value }) => value)
          .reduce((previousValue, currentValue) => {
            return previousValue + currentValue;
          }, 0);
      }),
      startWith(0)
    );
    this.hasDiscount$ = this.sum$.pipe(
      map(value => value > 10000), // PUT THE CORRECT VALUE HERE
      startWith(false)
    );
  }
}

HTML:

<mat-selection-list>
  <mat-list-option class="selected" *ngFor="let option of options" [value]="option.price">
    {{ option.name }} - ${{ option.price }}
  </mat-list-option>
</mat-selection-list>

<p>You have selected {{ matSelectionOptionsComponentRef.selectedOptions.selected.length }} 
  items with total sum of ${{ sum$ | async }}.</p>
<p *ngIf="hasDiscount$ | async">Your discount is 10%.</p>

DEMO

Related