Angular and Zod makes for nice combination for type validation

3 min read

Working with data from No-SQL databases like Firebase Firestore in your Angular applications can sometimes lead to unexpected type inconsistencies. This is where Zod, a powerful TypeScript-first schema declaration and validation library, can become a valuable ally.

Zod helps you define the expected shape of your data and validates it at runtime, catching issues before they cause problems in your UI. It's particularly handy for keeping data transformations—like converting database-specific types to standard JavaScript types—all in one place.

Let's look at how Zod can be used to validate user review data, specifically transforming a Firebase Timestamp.

// user-review.ts
import { z } from "zod";

export const UserReviewSchema = z.object({
  id: z.string(),
  userId: z.string().optional(),
  rating: z.number(),
  // Transform Firebase Timestamp automatically to Date object
  reviewDate: z.custom<{ toDate: () => Date }>().transform((ts) => ts.toDate()),
  // ...other properties
});

export type UserReview = z.infer<typeof UserReviewSchema>;

In this snippet:

  • UserReviewSchema defines the expected structure of a user review using z.object.
  • We specify types for id, userId, and rating.
  • The reviewDate field is a great example of Zod's transform capability. It takes a custom type (like a Firebase Timestamp with a toDate() method) and converts it into a standard JavaScript Date object. This means your Angular components receive a ready-to-use Date without needing manual mapping.
  • export type UserReview = z.infer<typeof UserReviewSchema>; automatically infers a TypeScript type (UserReview) directly from your Zod schema. This ensures your static types always match your runtime validation, providing excellent type safety throughout your application.

How to use it for validation

Once your schema is defined, integrating it into your data fetching logic is straightforward.

// reviews-api.ts
reviewsResource(productId: Signal<string | undefined>) {
    return resource({
      params: productId,
      loader: async (loaderParams) => {
        // Get reviews from firebase - then parse through zod
        return UserReviewSchema.array().parse(rawReviews);
      },
    });
}

In this reviewsResource example, after rawReviews are fetched (e.g., from Firebase), UserReviewSchema.array().parse(rawReviews) attempts to validate the incoming data against our schema. If the rawReviews don't conform to the UserReviewSchema (e.g., a missing required field, an incorrect type, or a malformed reviewDate that can't be transformed), Zod will throw a descriptive error, helping you pinpoint data inconsistencies early during development or even at runtime.

Why this combination is useful

Integrating Zod into your Angular projects, especially when dealing with less strictly typed data sources, offers several key benefits:

  • Runtime Safety: Catches unexpected data shapes or missing fields from your backend, preventing hard-to-debug undefined errors in your UI.
  • Single Source of Truth: Your Zod schema becomes the definitive source for your data's type and structure. All other types are inferred from it, and any data transformations are defined right there.
  • Easier Debugging: When an issue arises, Zod's clear error messages tell you exactly where your data deviates from the expected schema, making debugging much faster.
  • Cleaner Code: Keeps data mapping and validation logic consolidated and explicit, improving readability and maintainability.

Zod provides a robust yet simple way to ensure your data is always exactly what you expect, making your Angular applications more resilient and easier to develop.

Full code access and features for the Modern Angular Ecommerce App can be found here: Code Link

You may also like...

Loading comments...