angulargoogle-cloud-firestorengrxngrx-storengrx-entity

How to create item without passing id from angular app to firestore with ngrx/entity?


I am trying to perform a full CRUD operation using Angular + Cloud Firestore + NGRX/Entity.

Id is required parameter to perform ngrx/entity. But when I create an item from my angular form to my firestore, I don't know yet the id as it would be assigned automatically with the document id of my future creation.

How can I send data to firestore using ngrx/entity without sending the id?

I have an interface as follow:

Interface of entity:

export interface Recipe {
    id: string;
    recipeTitle: string;
}

actions.ts:

export const addRecipe = createAction(
    '[Recipe Add Component] Add Recipe',
    props<{ recipe: Recipe }>()
);

export const addRecipeSuccess = createAction(
    '[Recipe Add Effect] Add Recipe Success',
    props<{ recipe: Recipe }>()
);

export const addRecipeFailure = createAction(
    '[Recipe Add Effect] Add Recipe Failure',
    props<{ error: any }>()
);

Reducer.ts

// entity adapter
export const recipeAdapter: EntityAdapter<Recipe> = createEntityAdapter<Recipe>({
});

export interface RecipeState extends EntityState<Recipe> {
  error: any;
  selectedRecipe: Recipe;
}

export const initialState: RecipeState = recipeAdapter.getInitialState({
  error: undefined,
  selectedRecipe: undefined
});

export const recipeReducer = createReducer(
  initialState,

  on(RecipeActions.addRecipeSuccess, (state, action) => {
    return recipeAdapter.addOne(action.recipe, state);
  }),
  on(RecipeActions.addRecipeFailure, (state, action) => {
    return {
      ...state,
      error: action.error
    };
  }),

Effects.ts

  createRecipe$ = createEffect(() =>
        this.actions$.pipe(
            ofType(fromRecipeActions.addRecipe),
            mergeMap(action =>
                this.recipeService.createRecipe(action.recipe).pipe(
                    map(recipe => fromRecipeActions.addRecipeSuccess({recipe})),
                    catchError(error =>
                        of(fromRecipeActions.addRecipeFailure({ error }))
                    )
                )
            )
        )
    );

Create Component.ts


  createRecipe(formGroup): void {
    const recipeCreated = {
      recipeTitle: formGroup.recipeTitle
    };
    this.store.dispatch(actions.addRecipe({recipe: recipeCreated}));
    this.router.navigate(['tabs/recipe']);
  }

service.ts

 createRecipe(recipe): Observable<any> {
      return from(this.firestore.collection(`recipes`).add(recipe));
  }


  getRecipes(): Observable<any[]> {
    return this.firestore.collection<Recipe>('recipes').snapshotChanges().pipe(map(arr => {
      return arr.map(doc => {
        const data = doc.payload.doc.data();
        return {
          id: doc.payload.doc.id,
          ...data
        } as Recipe;
    });
  }));
}


  deleteRecipe(recipeId: string) {
    return from(this.firestore.collection<Recipe>('recipes/').doc(recipeId).delete());

  }

Solution

  • You can create a custom ID selector which would make id optional:

    export const recipeAdapter: EntityAdapter<Recipe> = createEntityAdapter<Recipe>({
       selectId: t => t.id || 0
    });