Skip to content

BenjaminCanape/AdvancedAngularLessons

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 

Repository files navigation

Advanced Angular Lessons

As a Front-end Lead Developer, I want to share my knowledge about front-end technologies and best practices. Here are a few lessons to boost your Angular expertise !


πŸ“š 1. Learn to master NgRx

βœ… Prerequisites

  • Angular CLI v19+
  • Node.js v22.12+
  • Basic knowledge of Angular components and services

πŸ“– Step 1: Get Started

Before diving into the exercises, it's crucial to understand what NgRx is, when to use it, and how to implement it.

πŸ‘‰ Read this article to learn first

The goal of the following exercises is to enhance a simple notebook app by replacing the current service-based state management with NgRx. The goal is to persist the notes even after a page refresh.

🎯 Try the final app: Live Demo
πŸ’» Check the final code: ngrx-notes-final

πŸ› οΈ Step 2: Setup Project

Follow these steps to get started:

  1. Clone the repository:
    git clone https://github.com/BenjaminCanape/AdvancedAngularLessons.git
  2. Checkout the start branch: ngrx-notes-start
  3. Install dependencies:
    npm i --legacy-peer-deps
  4. Run the application:
    ng serve --no-hmr
    (HMR is disabled to avoid page flickering issues.)

πŸ‹οΈ Exercises

πŸ“Œ Exercise 1: Configure NgRx

Steps:

  1. Install NgRx packages:

    npm install @ngrx/store @ngrx/effects @ngrx/store-devtools @ngrx/schematics
  2. Set up the store:

    • Create a store folder inside src/app
    • Inside the store folder, create:
      • app.state.ts
      • A note folder containing note.reducer.ts
  3. Define the note state inside note.reducer.ts:

    import { createReducer } from '@ngrx/store';
    
    export interface NoteState {}
    
    const initialState: NoteState = {};
    
    export const noteReducer = createReducer(initialState);
  4. Configure the global state in app.state.ts:

    import { NoteState } from './note/note.reducer';
    
    export interface AppState {
      notes: NoteState;
    }
  5. Provide the store in app.config.ts:

    const reducers: ActionReducerMap<AppState> = { notes: noteReducer };
    
    export const appConfig: ApplicationConfig = {
       providers: [
         provideStore(reducers);
       ]
    }

πŸŽ‰ Congratulations, your store is now configured!

❌ Common pitfalls

  • Forgetting to import provideStore in app.config.ts.
  • Incorrect reducer configuration leading to state issues.

❓ FAQ

  • Q: Why do I need to create an app.state.ts? A: It provides a centralized place to manage the global application state.

πŸ“Œ Exercise 2: Model and Initial State

πŸ”— Branch: ngrx-notes-exercise-2

  • Move note.model.ts to src/app/store/note
  • Modify note.reducer.ts to include notes and selectedId properties in the state.
  • Define an appropriate initial state.

❌ Common pitfalls

  • Storing unnecessary data in the state.
  • Not initializing properties correctly.

❓ FAQ

  • Q: What should be included in the state? A: Store only necessary properties in the state.

πŸ“Œ Exercise 3: Actions and Reducers

πŸ”— Branch: ngrx-notes-exercise-3

  • Create actions for adding, updating, deleting and selecting notes.
  • Modify the reducer to handle the new actions (some actions will be used later in effects, not here).

Example:

note.actions.ts

import { createActionGroup, props } from '@ngrx/store';

export const noteActions = createActionGroup({
  source: 'Note',
  events: {
    Add: props<{ note: NoteData }>(),
    'Add Success': props<{ note: Note }>(),
    'Add Failure': props<{ error: string }>(),
  },
});

note.reducer.ts

export const noteReducer = createReducer(
   initialState,
   on(noteActions.addSuccess, (state, { note }) => ({
   ...state,
   notes: [note, ...state.notes],
   })),
);

❌ Common pitfalls

  • Incorrect use of immutable state updates.

❓ FAQ

  • Q: Why do I need separate success and failure actions? A: It helps in handling API responses more efficiently.

πŸ“Œ Exercise 4: Side Effects (Effects)

πŸ”— Branch: ngrx-notes-exercise-4

  • Implement side effects using createEffect for handling asynchronous operations for adding, updating and deleting notes.
  • Call the API service inside the effect and dispatch success/failure actions.
  • Display user information (a snackbar for instance) when success or failure actions are dispatched
  • Select a note when the note has been added successfully.

Example:

note.effect.ts

@Injectable()
export class NoteEffects {
  private actions$ = inject(Actions);
  private noteService = inject(NoteService);

  addNote$ = createEffect(() =>
    this.actions$.pipe(
      ofType(noteActions.add),
      switchMap(({ note }) =>
        this.noteService.add(note).pipe(
          map(note => noteActions.addSuccess({ note })),
          catchError(error => of(noteActions.addFailure({ error })))
        )
      )
    )
  );
  
  addNoteSuccess$ = createEffect(() =>
      this.actions$.pipe(
      ofType(noteActions.addSuccess),
      switchMap(() => this.showSnackBar('notes.add_success'))
   ), { dispatch: false }
);
}

For information, you also need to modify the NoteService methods. We will clean this service soon, but in the first place, we will just edit the add, update and delete methods. At the end of the add method, you can return of(noteObject), in the update one, you can return of(note) and in the delete method, you can return of(true). It simulates the return of an api call as an Observable.

Be careful, your effects are not taken into account until your provide them in the application. So add this provideEffects([NoteEffects]), in the ApplicationConfig object.

❌ Common pitfalls

  • Not providing effects in ApplicationConfig.

πŸ“Œ Exercise 5: Selectors

πŸ”— Branch: ngrx-notes-exercise-5

  • Create selectors to retrieve specific data from the state: all notes, the selected id and the selected note.

Example:

note.selector.ts

export const selectNoteState = createFeatureSelector<NoteState>('notes');
export const selectAllNotes = createSelector(selectNoteState, state => state.notes);

❓ FAQ

  • Q: Can I select state from multiple features? A: Yes, by combining selectors from different feature states.

πŸ“Œ Exercise 6: Facade Pattern

πŸ”— Branch: ngrx-notes-exercise-6

  • Implement a facade to encapsulate store logic and simplify component interaction.
  • You want to expose the selectors and the methods to create, select, update and delete a note.
  • Edit your components using the NoteService to now use the NoteFacade instead
  • Clean the Note service from unnecessary signals and methods
  • Test the application after that, it's using the store and it works.

Example:

note.facade.ts

@Injectable({
   providedIn: 'root',
})
export class NoteFacade {
   private store = inject(Store<NoteState>);
   
   notes$ = this.store.select(selectAllNotes);
   
   add(note: NoteData) {
      return this.store.dispatch(noteActions.add({ note }));
   }
} 

πŸ“Œ Exercise 7: Debugging with Redux DevTools

πŸ”— Branch: ngrx-notes-exercise-7

  • Install and configure Redux DevTools for better state visualization and debugging with this documentation
  • Then play with Redux devtools to see which actions are dispatched when you click on buttons or write notes.

πŸ“Œ Exercise 8: Persisting State with Local Storage

πŸ”— Branch: ngrx-notes-exercise-8

  • Use ngrx-store-localstorage to persist state across browser refreshes. Install it and configure it with this documentation
  • When it's done, launch the application, add some notes, refresh the page and you will see that the store will be rehydrated with your previous data.

πŸŽ‰ Congratulations!

You have successfully completed the NgRx training! You are now equipped to build state-of-the-art Angular applications with NgRx.

πŸ”— Need help? Check the final implementation: ngrx-notes-final

About

Advanced training about Angular and NgRx

Topics

Resources

Stars

Watchers

Forks