Ngrx selector with .map return error cannot read property

600 Views Asked by At

Hello i created this ngrx selector

Path: src\app\store\navigation\navigation.selectors.ts

export const getNavigationState = createFeatureSelector<NavigationState>('navigation');

const getNavigation = createSelector(
  getNavigationState,
  (state) => {
    return state.entities;
  }
)

/**
 * Get Arrays Of all Categories and submenu
 */
export const getFullMenu = createSelector(
  getNavigation,
  (state) => {
    return state.menu
  }
)

/**
 * Get Arrays Of all Categories and submenu
 */
 export const fullMenuCat = createSelector(
  getFullMenu,
  (categories) => {
    categories.map((category) => {
      return {title: category.title, url: category.url}
    })
  }
)

I don't know why, but when i call my observable in my component i have this issue:

ERROR TypeError: Cannot read properties of undefined (reading 'map')

This is my data from data.json

Path: src\datas\subnav.json

[
  {
    "id": "angular",
    "title": "Angular stuff",
    "url": "/angular",
    "links":  [
      {
        "name": "Components",
        "url": "#"
      },
      {
        "name": "Directives",
        "url": "#"
      },
      {
        "name": "Services",
        "url": "#"
      },
      {
        "name": "Pipe",
        "url": "#"
      },
      {
        "name": "Other",
        "url": "#"
      }
    ]
  }
]

Those datas are used by ngrx effects

Path: src\app\store\navigation\navigation.effects.ts

  read$ = createEffect(() => this.actions.pipe(
    ofType(fromActions.Types.READ),
    switchMap(() =>  {
      return this.navigationService.getCategories().pipe(
        map(data => {
          return new fromActions.ReadSuccess(data);
        })
      )
    })
  ))

And finally this is the Navigation interface

Path: src\app\store\navigation\navigation.models.ts

import { Category } from "@app/models/backend";

export interface Navigation {
  isNavOpen: boolean,
  currentCategory: string,
  menu: Array<Category>,
  lastUrlVisited: string
}

and the Category interface

Path: src\app\models\backend\categorie\index.ts

export interface Category {
  id: string,
  title: string,
  url: string,
  links: Array<Link>
}

export interface Link {
  name: string,
  url: string,
}

this is the ts file of my component

Path: src\app\components\header\header.component.ts

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { select, Store } from '@ngrx/store';
/**State */
import * as fromRoot from '@app/store';
import * as fromNavigation from '@app/store/navigation';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderComponent implements OnInit {


  fullMenuCat$: Observable<any>;
  constructor(
    private store: Store<fromRoot.State>,

  ) {

  }

  ngOnInit(): void {
    this.store.dispatch(new fromNavigation.Read )
    this.fullMenuCat$ = this.store.pipe(select(fromNavigation.fullMenuCat));
  }

}

and the fullMenuCat$ observable call in my html

Path: src\app\components\header\header.component.html

<nav *ngIf="fullMenuCat$ | async  as fullMenuCat">
  <ol class="header__categories__nav">
    <li #link *ngFor="let menuCat of fullMenuCat" class="header__categories__navitem" (mouseover)="moveMarker($event)" (mouseout)="resetMarkerPos()" routerLinkActive="header__categories__navitem-active" >
      <a class="categories-a" routerLink="{{menuCat.url}}">{{menuCat.title}}</a>
    </li>
  </ol>
</nav>

I'm on this problem since 3 days and I don't know what I need to do. For more clarity here is a repo with my project: https://github.com/orphen92/angular-demo.git

You can run the projet with "npm run start command"

And a live url https://orphen92.github.io/angular-demo/angular

I try to create the most scallable architecture, so my code is explosed in many parts and i can't link all the code here (always for clarity).

I hope you can help me

1

There are 1 best solutions below

4
Nam On

You probably just need to give your store an initial state for that categories. Or use optional chaining combine with nullish coalescing to give your selector a default value if categories is empty, like this:

export const fullMenuCat = createSelector(
  getFullMenu,
  (categories) => {
    return categories?.map((category) => {
      return {title: category.title, url: category.url}
    }) ?? [];
  }
)

Since you're performing data fetching, categories is always going to be empty the first time your selector trying to read it, this is why you should always give your state some initial values