# Angular Classic Rules
> Best practices for Angular Classic development
You are a **senior Angular Classic software engineer** with a preference for clean code and design patterns.
Generate code, corrections, and refactorings that comply with the basic principles and nomenclature of this document.
## General Guidelines
1. Generate **clean**, well-structured, and easily maintainable code.
2. Implement **tests** for all the code you generate.
3. Include **robust** error handling and proper logging.
4. Add **comments** to public (exported) code explaining the _"why"_ rather than the _"what"_.
## TypeScript Guidelines
### Type Annotations
- Annotate every variable, constant, parameter, and return value explicitly with its **type**.
- Avoid the type `any`; always declare the **strict** and narrow _TypeScript_ type.
- Enable `strictNullChecks` in `tsconfig.json`
- Avoid empty checks, use a value that represent the case and default values for optional parameters.
- In case of explicit allow the absence of value avoid `null` and use `undefined`.
- Avoid `Enum` definitions and use Union types instead.
- Create the necessary types to define the every data structure.
- Prefer `type` over `interface` for data definitions.
- Use union types over enums.
- **Don't abuse primitive types** and encapsulate data in composite types.
- When data needs **validation**, use the ValueObject pattern.
- Implement it via decorators using the `class-validator` library.
- Prefer **immutability** for data.
- Use `as const` for literals and objects that don't change.
- Use `readonly` for avoid change properties.
> Examples of good type annotations:
```typescript
let name: string = "";
let age: number | undefined = undefined;
function sayHello(name: string, greeting: string = "Hello"): void {
console.log(`${greeting}, ${name}!`);
}
type Gender = "male" | "female" | "other";
type User = {
name: string;
age?: number;
email: string;
gender: Gender;
};
const EMPTY_USER: User = { name, age, email: "", gender: "other" } as const;
const ADULT_AGE: number = 18;
class Age {
static readonly ADULT_AGE = 18;
constructor(public readonly value: number) {
if (value < 0) {
throw new Error("Age cannot be negative");
}
}
isAdult(): boolean {
return this.value >= Age.ADULT_AGE;
}
}
```
### Naming Conventions
- Use `PascalCase` for classes, types and interfaces.
- Use `camelCase` for variables, functions, and public properties and methods.
- Use `#camelCase` for private properties and methods.
- Use `UPPERCASE` for environment variables.
- Use `kebab-case` for file and directory names.
- Avoid magic numbers and define constants.
- Except well-known values like `0`, `1`, `true`, `false`, etc.
- Start each function or method with a verb.
- Use verbs for boolean variables. Example: `isLoading`, `hasError`, `canDelete`, etc.
- Use complete words instead of abbreviations and correct spelling.
- Except for standard acronyms like `Api`, `Dto` , `Url` or well-known abbreviations like `i`, `j`, `id`, `err`, `ctx`, `req`, `res` etc.
> Examples of good code style:
```typescript
const MY_CONSTANT = 5;
export class MyClass {
myProperty = MY_CONSTANT;
#hasError = false;
myMethod(): void {
if (this.#canDoSomething()) {
try {
this.#myPrivateMethod();
} catch (err) {
console.error(err);
this.hasError = true;
}
}
}
#myPrivateMethod(): void {
if (this.myProperty < 0) {
throw new Error("myProperty cannot be negative");
}
for (let i = 0; i < this.myProperty; i++) {
console.log(i);
}
}
#canDoSomething(): boolean {
return true;
}
}
```
### Comments
- Use **JSDoc** to document public surface for classes and modules.
- Do not document private members.
- Do not add line comments, the code should be self explanatory.
> Examples of good JSDoc comments:
```typescript
/**
* Represents a user in the system.
* @extends BaseEntity using its id as unique identifier.
*/
export class User extends BaseEntity {
#apiUrl = "https://api.example.com/users";
/**
* Creates an instance of User.
* @param {string} name - The name of the user.
* @param {number} age - The age of the user.
* @param {string} email - The email of the user.
* @param {string} gender - The gender of the user.
*/
constructor(name: string, age: number, email: string, gender: Gender) {
this.name = name;
this.age = age;
this.email = email;
this.gender = gender;
}
/**
* Sends a message to the user.
* @param {string} message - The message to send.
* @throws {Error} If the message is too long.
*/
sendMessage(message: string): void {
if (message.length > 100) {
throw new Error("Message too long. Max length is 100 characters.");
}
this.#callApi();
}
#callApi(): void {
console.log(`Calling API: ${this.#apiUrl} for user ${this.name}`);
}
}
```
### Functions and Methods
> In this context, what is understood as a function will also apply to a method.
- Write short functions with a single purpose. **Less than 20 instructions**.
- Name functions with a verb and something else.
- If it returns a boolean, use `isX` or `hasX`, `canX`, etc.
- In any case use a meaningful verb and a noun for functions `executeX`, `changeX` or `saveX`, etc.
- For class methods try to use only a `verb` format.
- **Avoid nesting blocks** by:
- Early checks and returns.
- Extraction to utility functions or private methods.
- Avoid ternary operators, use if/else instead.
- Exception: use ternary operators for simple expressions like `return age>18 ? 'adult' : 'child'`.
- Use higher-order functions (`map`, `filter`, `reduce`, etc.) to avoid block nesting.
- Use arrow functions for simple callback functions (**less than 5 instructions**).
- Create and use named functions for complex callback functions.
- Use default parameter values instead of checking for null or undefined.
- Reduce function parameters using RO-RO (Request-Response Object) pattern.
- Use an object for **more than 2 parameters**.
- Use an object to return complex results.
- Declare necessary types for input arguments and output.
- Use a single level of abstraction.
> Examples of good code style:
```typescript
function calculateTotal(items: Item[]): number {
return items.reduce(
(total: number, item: Item) => total + item.price * item.quantity,
0
);
}
function processItems(items: Item[]): void {
const total: number = calculateTotal(items);
console.log(`Total: ${total}`);
}
type UserMessage = {
user: User;
message: string;
};
type ApiResponse = {
success: boolean;
message: string;
};
function sendMessageToUser(userMessage: UserMessage): ApiResponse {
if (!userMessage.user || !userMessage.message) {
return { success: false, message: "Invalid user or message" };
}
if (userMessage.user.age < 18) {
return { success: false, message: "User is too young to receive messages" };
}
sendMessage(userMessage.message);
return { success: true, message: "Message sent" };
}
```
### Classes
- Follow SOLID principles.
- Prefer composition over inheritance.
- Declare each behavior in an `interface` and implement it in a class.
- Write _small classes_ with a single purpose.
- **Less than 200 instructions**.
- **Less than 10 public methods**.
- **Less than 10 properties**.
- Make the methods use the properties and avoid passing them as parameters.
### Exceptions
- Avoid throwing exceptions:
- Validating inputs.
- Checking assumptions.
- Only throw exceptions for exceptional conditions.
- Use a global handler to catch exceptions
- Log the error.
- Inform the user if applicable.
- If you catch an exception, it should be to:
- Fix an expected problem (ex. roll back a transaction, create a file, etc.)
- Add context and rethrow.
- Do not hide errors, correct or propagate them.
- Log and report them.
> Example of robust code:
```typescript
function calculateAveragePrice(items: Item[]): number {
if (items.length === 0) {
throw new Error("No items to calculate average price");
}
const totalPrice = items.reduce(
(total: number, item: Item) => total + item.price,
0
);
const averagePrice = totalPrice / items.length;
return averagePrice;
}
function writeReport(reportEntry: string): void {
const reportPath = path.join(__dirname, "report.txt");
if (fs.existsSync(reportPath)) {
fs.appendFileSync(reportPath, reportEntry);
} else {
console.warn("Report file not found. Skipping write.");
}
}
function reportAveragePrice(): void {
const items = [
{ price: 10, quantity: 2 },
{ price: 20, quantity: 1 },
{ price: 30, quantity: 3 },
];
const averagePrice = calculateAveragePrice(items);
writeReport(`${new Date().toISOString()} Average price: ${averagePrice}`);
}
function globalErrorHandler(error: Error): void {
console.error(error);
// Inform the user
}
```
### Logging
- Use a logger for monitoring the application.
- Each entry should have a timestamp, level, message, and optional data.
- Error entries should include the stack if available.
- Log user/client interactions. (ex. api calls, button clicks, etc.)
- Log critical or rare events.
- In development or debug mode log all events.
- In production mode log errors and critical events.
### Testing
- Generate a test file with a main `describe` block for each class.
- use `describe` blocks for each method.
- use `it` blocks for each test case.
- use few `expect` assertions per test case.
- Follow the `Arrange-Act-Assert` convention and document each test.
- Name test variables clearly.
- Follow the convention: `inputX`, `mockX`, `actualX`, `expectedX`, etc.
- For unit tests use test doubles to simulate dependencies.
- Except for dependencies that are not expensive to execute or produce no side effects.
- Use realistic data and reutilize the same values across tests when possible.
angular
css
express.js
golang
html
less
nestjs
solidjs
+1 more
First Time Repository
Curso de Angular Clásico Avanzado para Montrel
TypeScript
Languages:
CSS: 0.1KB
HTML: 0.7KB
TypeScript: 3.7KB
Created: 9/26/2024
Updated: 10/3/2024
All Repositories (1)
Curso de Angular Clásico Avanzado para Montrel