Using Zod's schema to render a reusable and dynamic Angular table component
If you're already familiar with Zod, and you hear zod you probably link it with some kind of parsing or validation.
We also covered these aspects in previous blog posts, such as Why we should verify HTTP response bodies, and why we should use zod for this and Get easy access to Angular route and query parameters with zod.
But, zod can bring more to the table than just this. By leveraging a zod object instance we can get the details about an object structure at run-time, for example, to get a hold of all the properties including their types. This can be compared to reflection within the C# language.
In this blog post, we'll see how to use zod to dynamically build a (minimal) table as a reusable and typesafe Angular component.
For this, the following Person
schema will be used within the next snippets.
Retrieve the property names: using keyof().options
link
The first step is to know all the properties (or keys) of an object schema. These will later be used as the base to build the columns of the table.
To get the property names we can use the keyof()
method, which creates a ZodEnum
that contains all the property keys of the schema.
For our convenience, string
s are a bit easier to work compared to ZodEnum
s so let's convert the ZodEnum
tuple to a string collection by using the .options
property.
Modify which columns are (in)visible: using pick()
/omit()
link
It could be that not all properties need to be visible within the table.
To hide certain columns in a type-safe way the pick()
and omit()
methods can be used.
Both methods are used on a zod schema and are identical to the TypeScript's utility types.
These methods will either select the desired property names (pick()
), or ignore certain property names (omit()
).
This gives us the following result if we don't want to display the person's id in our example:
Render the table link
Using the property names collection it's now easy to iterate over each name to generate the table header columns. For each property name, a column is rendered within the table.
The snippets make use of the Control Flow syntax instead of structural directives. The Control Flow was introduced in Angular 17.
With a small change, the same technique can be used to render the body. Instead of simply iterating over all properties, first iterate over the collection of persons to render a row for each person in the collection. Using the property name as an index type, we can read the value of a property from an item within the collection.
Formatting: using _def.typeName
link
Of course not all columns need to be represented as is, some columns will require the need of some formatting. This could be done before the table gets rendered by mapping/transforming your data to the desired format, or we can build this into the table.
Zod's schema holds a definition that contains the type of the property, which is very helpful. For example:
We can use this information to render certain columns in a specific way, e.g. to use the date
pipe for date properties.
The method typeName(propertyName)
uses the shape
of the object to have the property information and is implemented as follows:
Reusable component link
This is all good, but we want a refactor the above code in a reusable component. This way, we can use the same table component for all of the zod schemas. Otherwise, the whole point of doing this will be lost.
For this, we can simply copy-paste the code (or use a refactor command from your favorite IDE) into a new component, and replace the static person types with a generic type. The table requires a schema and a collection as input to be rendered.
Bringing it all together link
Using the table component created in Reusable component, we can now easily render tables based on a zod schema.
Conclusion link
As we've seen in this post, zod can be used for more than just parsing and validation. Using zod's schema we get access to properties within an object's shape, including its type information. Based on this information it's possible to generate the table and render its data.
While this is just a very simple example, this can further be extended. There are also a few libraries that extend zod with the intent to generate fields/tables, e.g. zod-to-fields.
If you want to play with the example in this blog post see this TypeScript playground, or use this StackbBitz.
Outgoing links
- Get easy access to Angular route and query parameters with zod
- Why we should verify HTTP response bodies, and why we should use zod for this
Feel free to update this blog post on GitHub, thanks in advance!
Join My Newsletter (WIP)
Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.
Support me
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.