Skip to main content

Overview

BarberApp uses the Facade pattern to manage application state and business logic. All facades use Angular signals for reactive state management and are located in src/app/features/*/.
Facades act as an abstraction layer between components and repository services, providing a clean API and reactive state via signals.

AuthFacade

Manages authentication state and user session. Location: src/app/features/auth/auth.facade.ts:10
@Injectable({ providedIn: 'root' })
export class AuthFacade

Signals

Type: Signal<UserBase | null>
Access: Read-only
Location: auth.facade.ts:25
Current authenticated user or null if not logged in.
const currentUser = authFacade.user();
if (currentUser) {
  console.log(currentUser.firstName);
}
Type: Signal<boolean>
Access: Read-only (computed)
Location: auth.facade.ts:26
Computed signal that returns true if a user is authenticated.
const loggedIn = authFacade.isAuthenticated();
Type: Signal<boolean>
Access: Read-only
Location: auth.facade.ts:27
true when an authentication operation is in progress (login, register, logout).
Type: Signal<boolean>
Access: Read-only
Location: auth.facade.ts:28
true when checking initial authentication status on app load.
Type: Signal<string | null>
Access: Read-only
Location: auth.facade.ts:29
User-friendly error message from the last failed operation, or null if no error.

Methods

Location: auth.facade.ts:45Checks if a user is authenticated and loads their profile.Behavior:
  • Sets isCheckingAuth to true
  • Calls authService.isAuthenticated() and authService.getCurrentUser()
  • Updates user signal with the result
  • Sets isCheckingAuth to false when complete
Usage:
// In app initialization
await authFacade.checkAuthStatus();
Called automatically by authGuard and publicGuard
Location: auth.facade.ts:73Authenticates a user with email and password.Parameters:
  • email - User’s email address
  • password - User’s password
Behavior:
  • Sets isLoading to true
  • Calls authService.login()
  • Fetches user profile and updates user signal
  • Redirects user to appropriate dashboard based on role
  • Sets user-friendly error message if login fails
Error Codes:
  • auth/invalid-credential → “Correo o contraseña incorrectos.”
  • auth/too-many-requests → “Demasiados intentos fallidos. Intenta más tarde.”
Usage:
await authFacade.login('[email protected]', 'password123');
if (authFacade.error()) {
  console.error(authFacade.error());
}
Location: auth.facade.ts:102Registers a new user account.Parameters:
  • email - User’s email address
  • password - User’s password
Returns: Firebase UID of the created user, or void on errorBehavior:
  • Sets isLoading to true
  • Calls authService.register()
  • Returns UID but does NOT set user signal (you must call setUser() after creating user profile)
Error Codes:
  • auth/email-already-in-use → “Ese correo ya está registrado.”
  • auth/weak-password → “La contraseña es muy débil. Usá una más segura.”
Usage:
const uid = await authFacade.register('[email protected]', 'securePass');
if (uid) {
  // Create user profile in Firestore
  await userFacade.createUser({ id: uid, ...otherData });
  authFacade.setUser(newUser);
}
Location: auth.facade.ts:122Signs out the current user.Behavior:
  • Sets isLoading to true
  • Calls authService.logout()
  • Sets user signal to null
  • Clears any errors
Usage:
await authFacade.logout();
Location: auth.facade.ts:35Manually sets the user signal (typically after registration).Parameters:
  • user - User object to set, or null to clear
Usage:
authFacade.setUser(newlyCreatedUser);
Location: auth.facade.ts:186Redirects user to appropriate dashboard based on their role.Parameters:
  • user - User object with role property
Redirects:
  • UserRoles.CLIENT/dashboard/client
  • UserRoles.SPECIALIST/dashboard/specialist
  • UserRoles.ADMIN/dashboard/specialist
Usage:
authFacade.redirectUserByRole(authFacade.user()!);

UserFacade

Manages user data and operations. Location: src/app/features/auth/user.facade.ts:10
@Injectable({ providedIn: 'root' })
export class UserFacade

Signals

Type: Signal<UserBase[]>
Access: Read-only
Location: user.facade.ts:24
Array of users loaded by getUsersByRole().
Type: Signal<Client[]>
Access: Read-only
Location: user.facade.ts:25
Array of clients loaded by loadClientsForSpecialist().
Type: Signal<boolean>
Access: Read-only
Location: user.facade.ts:22
true when a user operation is in progress.
Type: Signal<string | null>
Access: Read-only
Location: user.facade.ts:23
Error message from the last failed operation.

Methods

Location: user.facade.ts:27Creates a new user profile in Firestore.Parameters:
  • user - Complete user object (Client or Specialist)
Throws: Error if creation failsUsage:
const newClient: Client = {
  id: uid,
  firstName: 'John',
  lastName: 'Doe',
  // ... other required fields
};
await userFacade.createUser(newClient);
Location: user.facade.ts:41Checks if a DNI is already registered.Parameters:
  • dni - National ID number to check
Returns: true if DNI exists, false otherwiseSide Effects:
  • Sets error signal to “Ese DNI ya está registrado.” if DNI exists
  • Clears error if DNI is available
Usage:
const exists = await userFacade.dniExists('12345678');
if (exists) {
  console.log(userFacade.error()); // "Ese DNI ya está registrado."
}
Location: user.facade.ts:53Updates an existing user’s data.Parameters:
  • updatedData - Partial user object with id required
Behavior:
  • Updates user in Firestore
  • If the updated user is the authenticated user, refreshes the authFacade.user() signal
Throws: Error if update failsUsage:
await userFacade.updateUser({
  id: userId,
  phone: '+1234567890',
  profilePictureUrl: newImageUrl
});
Location: user.facade.ts:78Loads all users with a specific role into the users signal.Parameters:
  • role - User role (e.g., 'client', 'specialist')
Updates: users signalUsage:
await userFacade.getUsersByRole(UserRoles.SPECIALIST);
const specialists = userFacade.users();
Location: user.facade.ts:94Fetches a single user by ID.Parameters:
  • id - User ID
Returns: User object or null if not foundUsage:
const user = await userFacade.getUserById('user123');
Location: user.facade.ts:107Loads all clients who have had appointments with a specific specialist.Parameters:
  • specialistId - Specialist’s user ID
Updates: clients signalBehavior:
  • Fetches specialist’s pending and completed appointments
  • Extracts unique client IDs
  • Loads client profiles
  • Updates clients signal
Usage:
await userFacade.loadClientsForSpecialist(specialistId);
const myClients = userFacade.clients();

AppointmentFacade

Manages appointment state and operations. Location: src/app/features/appointments/appointment.facade.ts:9
@Injectable({ providedIn: 'root' })
export class AppointmentFacade

Signals

Type: Signal<Appointment[]>
Access: Read-only
Location: appointment.facade.ts:21
Current user’s appointments loaded by loadUserAppointments().
Type: Signal<Appointment | null>
Access: Read-only
Location: appointment.facade.ts:24
Single appointment loaded by loadAppointmentById().
Type: Signal<Appointment[] | null>
Access: Read-only
Location: appointment.facade.ts:25
Completed appointments for a viewed client (used in medical records).
Type: Signal<boolean>
Access: Read-only
Location: appointment.facade.ts:22
true when an appointment operation is in progress.
Type: Signal<string | null>
Access: Read-only
Location: appointment.facade.ts:23
Error message from the last failed operation.

Methods

Location: appointment.facade.ts:42Creates a new appointment for the authenticated client.Parameters:
  • specialty - Selected specialty/service
  • specialist - Selected specialist
  • date - Appointment date and time
Returns: Created appointment object, or null if user is not a clientBehavior:
  • Verifies current user is a client
  • Generates appointment ID with AutoId.newId()
  • Creates appointment with PENDING status
  • Adds to appointments signal (sorted by date)
Usage:
const appointment = await appointmentFacade.createAppointment(
  selectedSpecialty,
  selectedSpecialist,
  appointmentDate
);
Location: appointment.facade.ts:80Loads all appointments for the current user.Updates: appointments signalBehavior:
  • If user is CLIENT: loads client appointments
  • If user is SPECIALIST: loads specialist appointments
  • Clears appointments if no user is authenticated
Usage:
await appointmentFacade.loadUserAppointments();
const myAppointments = appointmentFacade.appointments();
This method is automatically called by an effect when authFacade.user() changes.
Location: appointment.facade.ts:107Loads a single appointment by ID.Parameters:
  • id - Appointment ID
Updates: selectedAppointment signalBehavior:
  • First checks if appointment is already in appointments() array
  • If not cached, fetches from service
  • Updates selectedAppointment signal
Usage:
await appointmentFacade.loadAppointmentById(appointmentId);
const appointment = appointmentFacade.selectedAppointment();
Location: appointment.facade.ts:129Updates an existing appointment.Parameters:
  • id - Appointment ID
  • updates - Partial appointment object with fields to update
Updates:
  • appointments signal (updates the matching appointment)
  • selectedAppointment signal (if the ID matches)
Usage:
await appointmentFacade.updateAppointment(appointmentId, {
  status: AppointmentStatus.COMPLETED,
  diagnosis: { details: 'Patient is healthy' }
});
Location: appointment.facade.ts:153Loads completed appointments for a specific client (used in medical records).Parameters:
  • clientId - Client’s user ID
Updates: viewedClientAppointments signalUsage:
await appointmentFacade.loadCompletedAppointmentsByClientId(clientId);
const history = appointmentFacade.viewedClientAppointments();
Location: appointment.facade.ts:168Fetches specialist appointments filtered by status (does not update signals).Parameters:
  • specialistId - Specialist’s user ID
  • statuses - Array of appointment statuses
Returns: Array of matching appointmentsUsage:
const pendingAndCompleted = await appointmentFacade.getSpecialistAppointmentsByStatus(
  specialistId,
  [AppointmentStatus.PENDING, AppointmentStatus.COMPLETED]
);
Location: appointment.facade.ts:187Fetches specialist appointments within a date range.Parameters:
  • specialistId - Specialist’s user ID
  • startDate - Range start
  • endDate - Range end
Returns: Array of appointments in the date rangeUsage:
const weekAppointments = await appointmentFacade.getAppointmentsBySpecialistAndDateRange(
  specialistId,
  new Date('2026-03-01'),
  new Date('2026-03-07')
);
Location: appointment.facade.ts:208Fetches client appointments within a date range.Parameters:
  • clientId - Client’s user ID
  • startDate - Range start
  • endDate - Range end
Returns: Array of appointments in the date rangeUsage:
const monthAppointments = await appointmentFacade.getAppointmentsByClientAndDateRange(
  clientId,
  startOfMonth,
  endOfMonth
);
Location: appointment.facade.ts:38Manually sets the appointments signal.Parameters:
  • appointments - Array of appointments
Usage:
appointmentFacade.setAppointments(filteredAppointments);

SpecialtyFacade

Manages specialty data. Location: src/app/features/specialties/specialty.facade.ts:8
@Injectable({ providedIn: 'root' })
export class SpecialtyFacade

Signals

Type: Signal<Specialty[]>
Access: Read-only
Location: specialty.facade.ts:15
Array of all available specialties loaded by loadSpecialties().

Methods

Location: specialty.facade.ts:17Loads all specialties from Firestore.Updates: specialties signalBehavior:
  • Only fetches if specialties() is empty (caches result)
  • Updates specialties signal with all specialties
Usage:
await specialtyFacade.loadSpecialties();
const availableSpecialties = specialtyFacade.specialties();
This method is safe to call multiple times—it only fetches data once.

Signal Patterns

Reactive UI Updates

All facade signals are read-only and automatically update components:
component.ts
export class AppointmentListComponent {
  private appointmentFacade = inject(AppointmentFacade);

  // Signals automatically update the UI when data changes
  appointments = this.appointmentFacade.appointments;
  isLoading = this.appointmentFacade.isLoading;

  async ngOnInit() {
    await this.appointmentFacade.loadUserAppointments();
  }
}
template.html
@if (isLoading()) {
  <app-loading-spinner />
} @else {
  @for (appointment of appointments(); track appointment.id) {
    <app-appointment-card [appointment]="appointment" />
  }
}

Effects

Facades use effects to react to signal changes:
appointment.facade.ts
constructor() {
  effect(() => {
    // Clear appointments when user logs out
    if (this.authFacade.user() === null) {
      this._appointments.set([]);
      this._viewedClientAppointments.set(null);
    }
  });
}

Testing Facades

Example test for AuthFacade:
auth.facade.spec.ts
describe('AuthFacade', () => {
  let facade: AuthFacade;
  let authService: jasmine.SpyObj<AuthRepository>;

  beforeEach(() => {
    authService = jasmine.createSpyObj('AuthRepository', [
      'login', 'register', 'logout', 'isAuthenticated', 'getCurrentUser'
    ]);

    TestBed.configureTestingModule({
      providers: [
        AuthFacade,
        { provide: AUTH_REPOSITORY, useValue: authService }
      ]
    });

    facade = TestBed.inject(AuthFacade);
  });

  it('should update user signal after successful login', async () => {
    authService.login.and.returnValue(Promise.resolve());
    authService.getCurrentUser.and.returnValue(Promise.resolve(mockUser));

    await facade.login('[email protected]', 'password');

    expect(facade.user()).toEqual(mockUser);
    expect(facade.isAuthenticated()).toBe(true);
  });

  it('should set error on failed login', async () => {
    authService.login.and.returnValue(
      Promise.reject({ code: 'auth/invalid-credential' })
    );

    await facade.login('[email protected]', 'wrongpass');

    expect(facade.error()).toBe('Correo o contraseña incorrectos.');
    expect(facade.user()).toBeNull();
  });
});
When testing facades, mock the repository implementations and verify signal updates.

Build docs developers (and LLMs) love