
Angular Architecture
- 279 installs
- 614 repo stars
- Updated March 28, 2026
- gentleman-programming/gentleman-skills
angular-architecture is a Gentleman Skills agent skill that applies the Scope Rule, feature-based folder layout, and Angular style-guide patterns for developers structuring or scaling Angular applications.
About
angular-architecture is a curated Gentleman Skills skill (version 1.0) that codifies how Angular projects should be organized before components multiply across features. Its Scope Rule states that components used by one feature live under features/[feature]/components/, while components shared by two or more features move to features/shared/components/. The skill documents a src/app layout with features/, core/ singletons, and no .component.ts suffixes—files are named user-profile.ts because folders provide context. It aligns with angular.dev guidance on inject(), signal inputs, protected template members, and ng CLI commands such as ng g c features/products/components/product-card --flat. Reach for angular-architecture when bootstrapping or refactoring Angular repos so agents place code consistently.
- angular-architecture
- Development
Angular Architecture by the numbers
- 279 all-time installs (skills.sh)
- Ranked #1,409 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gentleman-programming/gentleman-skills --skill angular-architectureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 279 |
|---|---|
| repo stars | ★ 614 |
| Last updated | March 28, 2026 |
| Repository | gentleman-programming/gentleman-skills ↗ |
How should Angular components be organized by feature?
For development and infrastructure management.
Who is it for?
Angular developers or agents structuring multi-feature SPAs who need consistent feature, shared, and core boundaries.
Skip if: Skip angular-architecture for React or Vue structure, Angular runtime debugging, or form-validation specifics—use framework-specific debugging or forms skills.
When should I use this skill?
User asks where to place an Angular component, how to structure features/, or how to follow the Scope Rule.
What you get
Feature-scoped folder trees, Scope Rule placements, and ng CLI scaffolding commands aligned to the style guide.
- feature folder structure
- component placement map
By the numbers
- Skill metadata version 1.0 from gentleman-programming
- Scope Rule defines 2 placement tiers: single-feature vs shared across features
- Project template includes 4 top-level areas: features/, core/, app config, and routes
Files
Breaking Changes from Zod 3
// ❌ Zod 3 (OLD)
z.string().email()
z.string().uuid()
z.string().url()
z.string().nonempty()
z.object({ name: z.string() }).required_error("Required")
// ✅ Zod 4 (NEW)
z.email()
z.uuid()
z.url()
z.string().min(1)
z.object({ name: z.string() }, { error: "Required" })Basic Schemas
import { z } from "zod";
// Primitives
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();
// Top-level validators (Zod 4)
const emailSchema = z.email();
const uuidSchema = z.uuid();
const urlSchema = z.url();
// With constraints
const nameSchema = z.string().min(1).max(100);
const ageSchema = z.number().int().positive().max(150);
const priceSchema = z.number().min(0).multipleOf(0.01);Object Schemas
const userSchema = z.object({
id: z.uuid(),
email: z.email({ error: "Invalid email address" }),
name: z.string().min(1, { error: "Name is required" }),
age: z.number().int().positive().optional(),
role: z.enum(["admin", "user", "guest"]),
metadata: z.record(z.string(), z.unknown()).optional(),
});
type User = z.infer<typeof userSchema>;
// Parsing
const user = userSchema.parse(data); // Throws on error
const result = userSchema.safeParse(data); // Returns { success, data/error }
if (result.success) {
console.log(result.data);
} else {
console.log(result.error.issues);
}Arrays and Records
// Arrays
const tagsSchema = z.array(z.string()).min(1).max(10);
const numbersSchema = z.array(z.number()).nonempty();
// Records (objects with dynamic keys)
const scoresSchema = z.record(z.string(), z.number());
// { [key: string]: number }
// Tuples
const coordinatesSchema = z.tuple([z.number(), z.number()]);
// [number, number]Unions and Discriminated Unions
// Simple union
const stringOrNumber = z.union([z.string(), z.number()]);
// Discriminated union (more efficient)
const resultSchema = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.unknown() }),
z.object({ status: z.literal("error"), error: z.string() }),
]);Transformations
// Transform during parsing
const lowercaseEmail = z.email().transform(email => email.toLowerCase());
// Coercion (convert types)
const numberFromString = z.coerce.number(); // "42" → 42
const dateFromString = z.coerce.date(); // "2024-01-01" → Date
// Preprocessing
const trimmedString = z.preprocess(
val => typeof val === "string" ? val.trim() : val,
z.string()
);Refinements
const passwordSchema = z.string()
.min(8)
.refine(val => /[A-Z]/.test(val), {
message: "Must contain uppercase letter",
})
.refine(val => /[0-9]/.test(val), {
message: "Must contain number",
});
// With superRefine for multiple errors
const formSchema = z.object({
password: z.string(),
confirmPassword: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Passwords don't match",
path: ["confirmPassword"],
});
}
});Optional and Nullable
// Optional (T | undefined)
z.string().optional()
// Nullable (T | null)
z.string().nullable()
// Both (T | null | undefined)
z.string().nullish()
// Default values
z.string().default("unknown")
z.number().default(() => Math.random())Error Handling
// Zod 4: Use 'error' param instead of 'message'
const schema = z.object({
name: z.string({ error: "Name must be a string" }),
email: z.email({ error: "Invalid email format" }),
age: z.number().min(18, { error: "Must be 18 or older" }),
});
// Custom error map
const customSchema = z.string({
error: (issue) => {
if (issue.code === "too_small") {
return "String is too short";
}
return "Invalid string";
},
});React Hook Form Integration
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
email: z.email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof schema>;
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
</form>
);
}Keywords
zod, validation, schema, typescript, forms, parsing
Related skills
How it compares
Use angular-architecture for folder and naming conventions; pair with curated/angular/forms or performance skills for domain-specific Angular tasks.
FAQ
What is the Angular Scope Rule?
angular-architecture defines the Scope Rule as 'scope determines structure': a component used by one feature stays in features/[feature]/components/, while any component used by two or more features belongs in features/shared/components/ to prevent cross-feature coupling.
Why drop .component.ts file suffixes?
angular-architecture removes .component.ts, .service.ts, and .model.ts suffixes because the folder path already signals file role. A file at features/checkout/services/payment.ts is clearly a service without redundant naming noise.
Which Angular CLI commands does the skill recommend?
angular-architecture shows ng new for projects and flat generators such as ng g c features/products/components/product-card --flat for feature components and ng g g core/guards/auth --functional for core guards, keeping generated files aligned with the documented tree.