ngrx / platform

Reactive State for Angular
https://ngrx.io
Other
8.01k stars 1.97k forks source link

Signal Store - calling injected service but keep getting, is Not a Function error #4328

Closed dreamstar-enterprises closed 4 months ago

dreamstar-enterprises commented 4 months ago

Which @ngrx/* package(s) are the source of the bug?

signals

Minimal reproduction of the bug/regression with instructions

Store is quite simple:

Store

export const ContractStore = signalStore(
  {providedIn: 'root'},
  withState(initialState),

  withComputed(({contracts, searchQuery}, ) => {
    const contractService = inject(ContractService)
    return {
      sortedContracts: computed(() => {

        const sortedContracts = contractService.sortContractsByFirstName(contracts())

        })
        return sortedContracts
      }),
    }
  })
);

The service is quite large, but this is one of the functions inside it.

Service

 public sortContractsByFirstName(contracts: Array<AnyContractDTO>): Array<AnyContractDTO> {
    contracts.sort((a, b) => {
      if (a.personalInformation.firstName.toLowerCase() < b.personalInformation.firstName.toLowerCase()) {
        return -1;
      } else if (a.personalInformation.firstName.toLowerCase() > b.personalInformation.firstName.toLowerCase()) {
        return 1;
      } else {
        return 0;
      }
    });
    return contracts
  }

But I keep getting the error: ERROR TypeError: contractService.sortContractsByFirstName is not a function.

Screenshot 2024-05-12 at 22 33 00

Does anyone know why this might be?

Expected behavior

The function should just work

Versions of NgRx, Angular, Node, affected browser(s) and operating system(s)

NgRx Signal Store

Other information

For some reason, my other signal store does work, but i don't understand why nothing works here.

I would be willing to submit a PR to fix this issue

markostanimirovic commented 4 months ago

As the bug report description said, it's necessary to provide a reproduction via StackBlitz or GitHub. Otherwise, the issue may be closed without resolution.

Share a playground with reproduction and we will try to help you / fix a potential bug.

dreamstar-enterprises commented 4 months ago

I will try. It is definitely an Angular or NgRx bug.

I tried console logging the entire service, and got a strange result, as if the service wasn't being injected properly. Reducing the size of the service helped, but I don't see why that should make any difference....

See...https://stackoverflow.com/questions/78469347/angular-ngrx-signal-store-keep-getting-is-not-a-function-error

It's hard to reproduce when it seems like the bug is due to the size of the service...

dreamstar-enterprises commented 4 months ago

Some code: I get the same error here.. I do from contractStore...

  1. contractService.filterProject (previously this said, is not a function error, then I reduced to 300 lines, and it went away)
  2. But now, contractService.filterProject (see below... calls from a different store), and it says the same error (is not a function on the line this.filterModalStore.selectedUnitsNotZero())

Contract Service

protected filterModalStore = inject(FilterModalStore)

 public filterProject(contractFirstNameMatch: boolean, contract: AnyContractDTO) {
    const Contract = contract as ContractDTO;

    // filter for units
    let isUnitFound
    if(this.filterModalStore.selectedUnitsNotZero()){
      isUnitFound = this.filterModalStore.selectedUnits().includes(Contract.engagementDetails.unit )
    } else {
      isUnitFound = true
    }

    // filter for departments
    let isDepartmentFound
    if(this.filterModalStore.selectedDepartmentsNotZero()){
      isDepartmentFound = this.filterModalStore.selectedDepartments().includes(Contract.engagementDetails.department )
    } else {
      isDepartmentFound = true
    }

    return contractFirstNameMatch && isUnitFound && isDepartmentFound;
  }

FilterModalStore

export const FilterModalStore = signalStore(
  {providedIn: 'root'},
  withState(initialState),

  withComputed((store) => {
    return {
      selectedUnits: computed(() => {
        return store.filterModalMap()['contract-filtered-units']
      }),
      selectedDepartments: computed(() => {
        return store.filterModalMap()['contract-filtered-departments']
      }),
      selectedUnitsNotZero: computed(() => {
        return store.filterModalMap()['contract-filtered-units']?.selectedItemsState.length > 0
      }),
      selectedDepartmentsNotZero: computed(() => {
        return store.filterModalMap()['contract-filtered-departments']?.selectedItemsState.length > 0
      })
    }
  }),

  withComputed((store) => {
      const contractService = inject(ContractService)
      const contractStore = inject(ContractStore)
    return {
      filteredUnits: computed( () => {
        const uniqueUnits = contractService.distinctUnits(contractStore.contracts())
        if (store.selectedUnitsNotZero()) {
          return store.selectedUnits().selectedItemsState
        } else {
          return new Set(uniqueUnits)
        }
      }),
      filteredDepartments: computed( () => {
        const uniqueDepartments = contractService.distinctDepartments(contractStore.contracts())
        if (store.selectedDepartmentsNotZero()) {
          return store.selectedDepartments().selectedItemsState
        } else {
          return new Set(uniqueDepartments)
        }
      }),
    }
  }),

  withMethods((store) => {
    return {
      updateFilterModalMap(modalType: string, modalData: any) {
        const updatedFilterModalMap = store.filterModalMap()[modalType] = modalData
        patchState( store, { filterModalMap: updatedFilterModalMap})
      }
    }
  }),

  withHooks((store) => {
    return {
      onInit() {},
      onDestroy() {},
    };
  }),
);
rainerhahnekamp commented 4 months ago

@dreamstar-enterprises, withComputed doesn't give you the store with all its methods, only the slices of the state.

So you cannot call filterModalMap, etc. when they have been defined in withMethods.

I am wondering why your IDE is not already telling you that.

dreamstar-enterprises commented 4 months ago

I think I found the problem. The injection of contractService.(any function) was failing silently (just saying is not a function), because..

  1. I called this.filterModalStore inside contractStore, that injected contractService, that had filterModalStore injected in it...
  2. But then in filterModalStore in the withComputed of filterModalStore, I injected contractService... (creating a sort of circular dependency).

When I removed the injection from filterModalStore, I didn't get those errors contractService.(any function), saying that it was not a function (my original 1500 line service worked between the contracStore and contractService)...

The solution would be to move the below logic into contractService or contracStore, to avoid the circular injections.

 withComputed((store) => {
      const contractService = inject(ContractService) --> cannot do this, since this already has filterModalStore injected
      const contractStore = inject(ContractStore) --> probably cannot do this either
    return {
      filteredUnits: computed( () => {
        const uniqueUnits = contractService.distinctUnits(contractStore.contracts())
        if (store.selectedUnitsNotZero()) {
          return store.selectedUnits().selectedItemsState
        } else {
          return new Set(uniqueUnits)
        }
      }),
      filteredDepartments: computed( () => {
        const uniqueDepartments = contractService.distinctDepartments(contractStore.contracts())
        if (store.selectedDepartmentsNotZero()) {
          return store.selectedDepartments().selectedItemsState
        } else {
          return new Set(uniqueDepartments)
        }
      }),
    }
  }),
rainerhahnekamp commented 4 months ago

Great, happy you found the answer. Could you please close the issue, if it is fixed for you?

markostanimirovic commented 4 months ago

Thanks Rainer!