AutoComplete for suggestions with array of type string

Goal: Add AutoComplet String

Add an AutoComplete for suggestions with array of type string as a search criteria input field for your search page.

1. Parameters

Please define the member for your <%= featurePropertyName %>SearchCriteriasSchema here

Please replace all occurences of exampleStringArray with the actual name in the following code snippets.

Adapt in File: <feature>-search.parameters.ts

    exampleStringArray: z
        .union([z.string(), z.array(z.string())])
        .transform((v: string | string[] | undefined): string[] | undefined =>
        v instanceof Array || !v
            ? (v as string[] | undefined)
            : ([v] as string[]),
        )
        .transform((v: string[] | undefined) => v?.map((e) => e))
        .optional(),

2. State & viewmodel

Add additional array member for the autocomplete suggestions in your state and viewmodel:

Adapt in Files: <feature>-search.viewmodel.ts and <feature>-search.state.ts

...
    exampleStringArrayOptions: string[]
...

3. Actions

Create following actions to handle the events regarding autocomplete:

Adapt in File: <feature>-search.actions.ts

...
    events: {
        'exampleStringArray data text entered': props<{ exampleStringArrayInputValue: string }>(),
        'exampleStringArray data received ': props<{ exampleStringArrayOptions: string[] }>(),
        'exampleStringArray data loading failed': emptyProps(),
        'exampleStringArray selected': props<{ selectedExampleStringArrayValue: string }>(),
        'exampleStringArray unselected': props<{ unSelectedExampleStringArrayValue: string }>(),
    },
...

4. HTML

Add the following code to your formGroup in the html file:

Adapt in File: <feature>-search.component.html

    <span class="p-float-label">
        <p-autoComplete
            id="exampleStringArray"
            formControlName="exampleStringArray"
            (completeMethod)="searchExampleStringArrayData($event)"
            [suggestions]="vm?.exampleStringArrayOptions ?? []"
            [forceSelection]="true"
            [multiple]="true" <-- ONLY NECESSARY IF MULTIPLE VALUES CAN BE SELECTED
            [showEmptyMessage]="true"
            [emptyMessage]="
            vm?.exampleStringArrayOptions?.length === 0
                ? ('YOUR_PRODUCT_SEARCH.CRITERIA.EXAMPLE_ID_NOT_FOUND' | translate)
                : ''
            "
            (onSelect)="selectExampleStringArrayValue($event)"
            (onUnselect)="unSelectExampleStringArrayValue($event)"
        >
        </p-autoComplete>
        <label for="exampleStringArray">{{
            'YOUR_PRODUCT_SEARCH.CRITERIA.ID' | translate
        }}</label>
    </span>

5. Component

Add the respective methods to handle the different events:

Adapt in File: <feature>-search.component.ts

...
    searchExampleStringArrayData(event: AutoCompleteCompleteEvent) {
      this.store.dispatch(
        <feature>SearchActions.exampleStringArrayDataTextEntered({
            exampleStringArrayInputValue: event.query,
        }),
      );
    }

    selectExampleStringArrayValue(event: AutoCompleteSelectEvent) {
      this.store.dispatch(
        <feature>SearchActions.exampleStringArraySuggestionSelected({
            selectedExampleStringArrayValue: event.value,
        }),
      );
    }

    unSelectExampleStringArrayValue(event: AutoCompleteUnselectEvent) {
      this.store.dispatch(
        <feature>SearchActions.exampleStringArraySuggestionUnselected({
            unSelectedExampleStringArrayValue: event.value,
        }),
      );
    }
...

6. Reducers

In the reducers file you need to define the functions:

Adapt in File: <feature>-search.reducers.ts

...
  on(
    <%= featureClassName %>SearchActions.exampleStringArrayDataReceived,
    (state: <%= featureClassName %>SearchState, { exampleStringArrayOptions }): <%= featureClassName %>SearchState => ({
      ...state,
      exampleStringArrayOptions: exampleStringArrayOptions,
    }),
  ),
  on(
    <%= featureClassName %>SearchActions.exampleStringArrayDataLoadingFailed,
    (state: <%= featureClassName %>SearchState): <%= featureClassName %>SearchState => ({
      ...state,
      exampleStringArrayOptions: [],
    }),
  ),
  on(
    <%= featureClassName %>SearchActions.exampleStringArraySuggestionSelected,
    (
      state: <%= featureClassName %>SearchState,
      { selectedExampleStringArrayValue },
    ): <%= featureClassName %>SearchState => {
      const isValuePresent =
        state.exampleStringArraySelectedValues.includes(selectedExampleStringArrayValue);
      return {
        ...state,
        exampleStringArraySelectedValues: isValuePresent
          ? state.exampleStringArraySelectedValues
          : [...state.exampleStringArraySelectedValues, selectedExampleStringArrayValue],
        exampleStringArrayOptions: [],
      };
    },
  ),
  on(
    <%= featureClassName %>SearchActions.exampleStringArraySuggestionUnselected,
    (
      state: <%= featureClassName %>SearchState,
      { unSelectedExampleStringArrayValue },
    ): <%= featureClassName %>SearchState => ({
      ...state,
      exampleStringArraySelectedValues: state.exampleStringArraySelectedValues.filter(
        (exampleStringArray) => exampleStringArray !== unSelectedExampleStringArrayValue,
      ),
      exampleStringArrayOptions: [],
    }),
  ),
...

7. Selectors

Add the missing selectors:

Adapt in File: <feature>-search.selectors.ts

...
    export const select<%= featureClassName %>SearchViewModel = createSelector(
      ...
      <feature>SearchSelectors.
      selectExampleStringArrayOptions,
      ...
      (
        ...
        exampleStringArrayOptions,
        ...
      ): <%= featureClassName %>SearchViewModel => ({
        ...
        exampleStringArrayOptions,
        ...
      }),
    );
...

8. Effects

Create the effect for getting the suggestions

Adapt in File: <feature>-search.effects.ts

...
    searchExampleStringArray$ = createEffect(() =>
      this.actions$.pipe(
        ofType(<%= featureClassName %>SearchActions.exampleStringArrayDataTextEntered),
        mergeMap((action) => {
          return this.<feature>Service
            .searchExampleStringArray(action.exampleStringArrayInputText)
            .pipe(
              map((response) =>
                <%= featureClassName %>SearchActions.exampleStringArrayDataReceived({
                  exampleStringArrayOptions: response.exampleStringArray, <-- NAME OF THE MEMBER WHICH IS DEFINED IN THE RESPONSE OBJECT
                }),
              ),
              catchError(() =>
                of<%= featureClassName %>SearchActions.exampleStringArrayDataLoadingFailed()),
              ),
            );
        }),
      ),
    );
...
Don’t forget to add the translations to your de.json and en.json.