GovAlta / ui-components

ui-components contains the code you need to start building a user interface for Government of Alberta platforms and services.
Apache License 2.0
16 stars 25 forks source link

Dropdown: Can't remove items from the Dropdown dynamically #2170

Open ArakTaiRoth opened 1 month ago

ArakTaiRoth commented 1 month ago

Info

Reported by Francis Agyapong

Removing items generally works dynamically from the dropdown using the unique key idea raised earlier. However, it seems impossible to remove the final item from the Dropdown.

You can see the issue on this Stackblitz.

If that link is dead, here is the code for Angular:

<div>
  <h3>Select Item</h3>

  <pre style="text-overflow: wrap;">{{ itemsToAdd | json }}</pre>

  <div style="display: flex; gap: 1rem;">
    <goa-form-item label="Items" style="display: block; max-width: 50%;">
      <goa-dropdown
        name="item"
        placeholder="Select an item"
        notfilterable
        (_change)="addItem($event)"
      >
        <goa-dropdown-item
          *ngFor="let item of itemsToAdd; trackBy: generateUniqueKey"
          mount="reset"
          value="{{ item.id }}"
          label="{{ item.id }}. {{ item.label }}"
        ></goa-dropdown-item>
      </goa-dropdown>
    </goa-form-item>

    <!-- Browser select -->
    <goa-form-item label="Browser Select">
      <select style="padding: 0.75rem; min-width: 30ch;">
        <option value=""></option>
        @for (item of itemsToAdd; track item.id) {
        <option [value]="item.id">{{ item.id }} {{ item.label }}</option>
        }
      </select>
    </goa-form-item>
  </div>
</div>

<hr />

<div>
  <h3>Added Items</h3>

  <ul>
    @for (item of addedItems; track item.id) {
    <li>{{ item.id }}. {{ item.label }}</li>
    }
  </ul>
  <pre style="text-overflow: wrap;">{{ addedItems | json }}</pre>
</div>

<goa-button
  (_click)="reset()"
  [attr.disabled]="addedItems.length < 1 ? true : null"
  >Reset</goa-button
>
import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

import '@abgov/web-components';
import { AngularComponentsModule } from '@abgov/angular-components';

type Item = { id: string; label: string };

@Component({
  standalone: true,
  selector: 'app-dynamic-dropdown-test',
  templateUrl: './app-dynamic-dropdown-test.component.html',
  //styleUrl: './app-dynamic-dropdown-test.component.scss',
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  imports: [CommonModule, AngularComponentsModule, ReactiveFormsModule],
})
export class DynamicDropdownTestCompoennt implements OnInit {
  public itemsToAdd: Item[] = [];
  public addedItems: Item[] = [];

  public ngOnInit() {
    setTimeout(() => {
      this.itemsToAdd = [
        { id: '1', label: 'Maracas' },
        { id: '2', label: 'Cowbell' },
        { id: '3', label: 'Stylophone' },
        { id: '4', label: 'Guitar' },
        { id: '5', label: 'Bongos' },
      ];
    }, 500);
  }

  public addItem({ detail: { value } }: any) {
    const itemIndex = this.itemsToAdd.findIndex((x) => x.id === value);
    if (itemIndex < 0) {
      return;
    }

    const item = this.itemsToAdd[itemIndex];
    this.itemsToAdd = [
      ...this.itemsToAdd.slice(0, itemIndex),
      ...this.itemsToAdd.slice(itemIndex + 1),
    ];
    this.addedItems = [...this.addedItems, item];
  }

  public reset() {
    const items = [...this.itemsToAdd, ...this.addedItems];
    items.sort((a, b) => +a.id - +b.id);
    this.itemsToAdd = items;
    this.addedItems = [];
  }

  public generateUniqueKey(index: any, item: any): string {
    return `${item}_${index}_${Math.random()}`;
  }
}

More Info

There are a couple problems with what's shown in the above Stackblitz. It isn't using the latest version (the problem exists if you update the version). It's not using Reactive Forms (I'm not sure if that makes a difference [it shouldn't]).

Acceptance Criteria

  1. Should be able to remove the final item from the Dropdown
jim-muirhead commented 1 month ago

Because this component is a part of the component package, we are not able to update some other components that have been fixed, because it will break some key functionality in the app. The other component in the package that we are anxious to implement is the Chip component. Without the fix to the text overflow issue in the Chip component we have usability issues with a key part of our app's interface - please refer to the following screenshot.

image
Spark450 commented 1 month ago

@syedszeeshan could you take a look at this to see if the examples that you put together for the mount issue resolve this issue?

ArakTaiRoth commented 1 month ago

@ArakTaiRoth Investigate this issue if setting up random keys for each Dropdown item fixes the issue

syedszeeshan commented 1 month ago

@syedszeeshan could you take a look at this to see if the examples that you put together for the mount issue resolve this issue?

@Spark450

I tried this below

 <goa-dropdown-item
          *ngFor="
            let item of itemsToAdd;
            first as isFirst;
           trackBy: generateUniqueKey
          "
          mount="{{ isFirst ? 'reset' : 'append' }}"
          value="{{ item.id }}"
          label="{{ item.id }}. {{ item.label }}"
        ></goa-dropdown-item>

alongwith this method

  generateUniqueKey(index: number, item: Item): any {
    return Math.random();
  }

and, after the above change..... as you select an item, it gets removed from the first dropdown and shows up in the added items list. Resetting brings everything back to how it was initially.

ArakTaiRoth commented 1 month ago

Closing as both me and Syed were able to resolve the issue, this my Stackblitz link with the issue resolved, https://stackblitz.com/edit/stackblitz-starters-rv7zkz?file=src%2Fdynamic-dropdown-t[…]%2Fdynamic-dropdown-test%2Fdynamic-dropdown-test.component.ts

ArakTaiRoth commented 1 month ago

Re-opening this issue, as there is still an issue present. This is detailed in the edited issue above.