You are an expert in Angular, SASS, and TypeScript, focusing on scalable web development.
Key Principles
- Provide clear, precise Angular and TypeScript examples.
- Apply immutability and pure functions where applicable.
- Favor component composition for modularity.
- Use meaningful variable names (e.g., `isActive`, `hasPermission`).
- Use kebab-case for file names (e.g., `user-profile.component.ts`).
- Prefer named exports for components, services, and utilities.
TypeScript & Angular
- Define data structures with interfaces for type safety.
- Avoid `any` type, utilize the type system fully.
- Organize files: imports, definition, implementation.
- Use template strings for multi-line literals.
- Utilize optional chaining and nullish coalescing.
- Use standalone components when applicable.
- Leverage Angular's signals system for efficient state management and reactive programming.
- Use the `inject` function for injecting services directly within component, directive or service logic, enhancing clarity and reducing boilerplate.
- Use the `effect` function for side effects.
- Use the `input` function for input properties.
- Use the `output` function for output properties.
- Use self closing tags for components.
File Naming Conventions
- `*.component.ts` for Components
- `*.service.ts` for Services
- `*.module.ts` for Modules
- `*.directive.ts` for Directives
- `*.pipe.ts` for Pipes
- `*.spec.ts` for Tests
- All files use kebab-case.
Code Style
- Use single quotes for string literals.
- Indent with 2 spaces.
- Ensure clean code with no trailing whitespace.
- Use `const` for immutable variables.
- Use template strings for string interpolation.
Angular-Specific Guidelines
- Use async pipe for observables in templates.
- Implement lazy loading for feature modules.
- Ensure accessibility with semantic HTML and ARIA labels.
- Utilize deferrable views for optimizing component rendering, deferring non-critical views until necessary.
- Incorporate Angular's signals system to enhance reactive programming and state management efficiency.
- Use the `NgOptimizedImage` directive for efficient image loading, improving performance and preventing broken links.
Import Order
1. Angular core and common modules
2. RxJS modules
3. Other Angular modules
4. Application core imports
5. Shared module imports
6. Environment-specific imports
7. Relative path imports
Error Handling and Validation
- Use proper error handling in services and components.
- Use custom error types or factories.
- Implement Angular form validation or custom validators.
Testing
- Follow the Arrange-Act-Assert pattern for tests.
Performance Optimization
- Optimize ngFor with trackBy functions.
- Use pure pipes for expensive computations.
- Avoid direct DOM manipulation; use Angular’s templating system.
- Optimize rendering performance by deferring non-essential views.
- Use Angular’s signals system to manage state efficiently and reduce unnecessary re-renders.
- Use the `NgOptimizedImage` directive to enhance image loading and performance.
Security
- Prevent XSS with Angular’s sanitization; avoid using innerHTML.
- Sanitize dynamic content with built-in tools.
Key Conventions
- Use Angular’s DI system and the `inject` function for service injection.
- Focus on reusability and modularity.
- Follow Angular’s style guide.
- Optimize with Angular's best practices.
- Focus on optimizing Web Vitals like LCP, INP, and CLS.
Reference
Refer to Angular’s official documentation for best practices in Components, Services, and Modules.
<!-- ####################################################################### -->
# Project Architecture
We follow a layered architecture to maintain clear boundaries between different responsibilities within the application. Each layer is designed to handle specific concerns and interact with others in a well-defined manner. This structure ensures separation of concerns and provides a scalable, testable, and maintainable codebase.
1. **Data Layer** (Infrastructure Boundary)
- **Purpose**
Acts as the boundary for communication between the application and external systems such as the backend, local storage, or IndexedDB.
- **Responsibilities**
- Uses Data Transfer Objects (DTOs) to communicate with external systems.
- Maps DTOs to Entities/Models used within the app, ensuring the domain layer stays isolated from the specifics of external data formats.
- Contains swappable concrete implementations, allowing the data layer to be replaced or updated without affecting the rest of the application.
- **Boundary**
The Data Layer directly interacts with external systems (e.g., APIs, databases) but never exposes these details to other layers. It ensures that the rest of the application remains agnostic to the specifics of data storage or retrieval.
2. **Domain Layer** (Core Business Logic Boundary)
- **Purpose**
Represents the core business logic of the application. This layer is independent of the external world and encapsulates the rules that drive the application.
- **Responsibilities**
- Contains Use Cases that define the business operations of the application.
Defines Models used across the app, representing the core data structures.
- Completely decoupled from the other layers and directly interacts with nothing outside itself. It only depends on abstractions (interfaces) defined and implemented in other layers.
- **Boundary**
The Domain Layer is the most abstract layer and sits at the core of the application. It has no dependencies on infrastructure or presentation concerns. It communicates with other layers via interfaces, ensuring that the business logic is isolated from external changes or implementation details.
3. Presentation Layer (UI Boundary)
- **Purpose**
Manages the visual elements and user interactions. This layer is responsible for rendering data and responding to user inputs.
- **Responsibilities**
- Displays data to the user through components and pages.
- Uses Use Cases from the Domain Layer to trigger business logic and update the UI based on state changes.
- Manages application state through stores.
- Reponsible for application logic in combination with state/stores.
- **Boundary**
The Presentation Layer only interacts with the Domain Layer through use cases and state stores. It does not directly handle business logic or manage data persistence. This ensures that UI concerns are kept separate from the core application logic.
## Emphasized Boundaries:
- The Data Layer is entirely focused on external communication and ensures that no external system logic leaks into the Domain or Presentation layers.
- The Domain Layer defines all business logic in use cases and models, and is isolated from the details of data storage and user interface.
- The Presentation Layer is solely concerned with UI rendering and interacting with the domain logic via use cases and state management(application logic), without ever dealing with business rules directly.
## System Advantages
The clear architectural boundaries provide several key benefits:
- **Separation of Concerns**: Each layer is responsible for one specific area of functionality, reducing complexity and improving maintainability.
- **Testability**: Isolated layers and clear boundaries make it easy to mock dependencies and test each layer independently.
- **Loose Coupling**: The core business logic (Domain Layer) is decoupled from the infrastructure (Data Layer) and presentation (UI Layer). Changes in one layer typically don't affect others.
- **Modularity**: Layers are independent, so individual components (like repositories or UI components) can be updated or replaced without affecting the entire system.
- **Scalability**: The system’s modularity makes it easy to scale individual layers or features, enabling teams to work independently on different parts of the system without overlap.
- **Debuggability**: Isolated layers make it easier to trace issues to specific areas of the application.
## State Management
For state management, we use the ngrx library. However, due to Angular's evolving approach towards Signals, we decided to use SignalStore from ngrx. This new state management system:
- Follows a functional approach rather than the traditional object-oriented model.
- Is easy to use, scalable, and integrates well with rxjs.
- Provides utilities to manage application state effectively without tightly coupling the state management to any specific layer.
- By using SignalStore, we maintain a clear boundary between state management and other layers, preserving the decoupling and separation of concerns that is central to our architecture.
## Disadvantages
While the architecture offers numerous advantages, there are a few trade-offs and challenges to consider:
- **Increased Complexity**
Introducing multiple layers can increase the overall complexity of the project, especially for small applications. Developers need to be familiar with the architecture's boundaries and responsibilities to navigate the codebase effectively.
- **Longer Development Time (Initial Setup)**
Setting up the layers, especially with proper abstractions and dependency injection, can initially take more time compared to simpler architectures. The upfront effort to design and implement these boundaries may delay early development, but once it's setup is done developers can go with the flow with ease.
- **Potential Overhead**
For small or simple features, the strict separation into layers may introduce unnecessary overhead. Simple use cases could become over-engineered by adding multiple layers where a direct approach might suffice. This is why we allow custom few shortcuts to reduce the overhead in case of simple use cases.
- **Learning Curve**
Developers unfamiliar with this architecture may face a learning curve, especially in understanding the different layers' boundaries and how to interact with each layer appropriately.
## Why We Should Adopt This Architecture
Despite the understandable disadvantages, the layered architecture offers substantial benefits, particularly for larger and more complex applications(our case). Here's why it makes sense to adopt this approach:
- **Scalability**
As the application grows, the need for a maintainable, scalable structure becomes more critical. The modular nature of this architecture allows different teams to work on isolated layers without interference. This scalability is essential for long-term projects.
- **Maintainability**
With clearly defined boundaries, the system becomes easier to maintain. Each layer can evolve independently, and new features can be added without affecting the entire application. This reduces the risk of introducing bugs when modifying one part of the system.
- **Testability**
Isolating each layer makes testing easier. The separation allows us to mock dependencies, unit test use cases and repositories, and verify the system's behavior without complex setups. This is crucial for maintaining high code quality.
- **Loose Coupling**
The separation of concerns ensures that the business logic is independent of infrastructure and UI concerns. This decoupling makes it easier to change external systems or swap out libraries without affecting the core application.
- **Future-Proofing**
Adopting a layered architecture sets the foundation for handling future requirements and technologies. As new frameworks, tools, or patterns emerge, the clear boundaries between layers ensure that updates or changes can be made with minimal disruption to the overall system.
<!-- ####################################################################### -->
# Coding Standards for Angular Projects
### 1. Pages/Components
- **Avoid Deep Nesting:** Keep the component and template structure as flat as possible for better readability and maintainability
- **Use Signal for Reactive Properties:** Use Angular’s new signal type for reactive properties to improve reactivity and performance.
- **Optimize Change Detection:** Always use `ChangeDetectionStrategy.OnPush` in components for better performance and reduced unnecessary checks.
- **File Size Limit:** Limit each file to a maximum of 200 lines to promote readability and maintainability.
- **Reusable Components:** Implement reusable components for isolated UI elements or logic that can be reused across different parts of the application.
- **Error Handling:** Always anticipate and handle expected errors (e.g., API failures, user input errors) to improve reliability.
- **Button Disable on API Calls:** All buttons triggering API calls must be disabled upon click and display a loading state to prevent multiple submissions.
- **Responsive Design:** Ensure UI components and pages are fully responsive and adapt properly across all target devices (desktop, tablet, mobile).
- **Separate Logic into Units:** Isolate complex logic into separate classes or use cases to keep components focused on UI concerns and ensure reusability.
- **Standardize with PrimeNG:** Prefer using PrimeNG components as the standard UI library and apply custom styles where necessary for consistency.
### 2. App Styles & SCSS
- **Tailwind Integration:**
- You may use Tailwind CSS classes in the templates for quick styling, but follow these rules:
- Short and Concise: Use Tailwind directly in templates only when the classes are short and easy to read.
- Use @apply for Long Classes: If the Tailwind classes are long or complex, move them into SCSS files and apply them using the @apply directive.
- **SCSS Organization:** Organize SCSS files by grouping styles into clear sections like color, layout, fonts, etc., with each section separated by a line.
- **Design System:** Define a basic theme or minimalistic design system using variables (colors, spacing, typography) to maintain consistency across the app.
- **Avoid Repetition:** Do not repeat styles in different components. Create shared classes for common styles and reuse them.
- **Component Animations:** Leverage Angular’s built-in animation API to add animations to components for smooth transitions and improved UX.
### 3. Logic and Code Structure
- **Keep Code Simple and Expressive:** Write code that is easy to read and understand. Avoid overcomplicating solutions.
- **Avoid Nested Conditionals:** Minimize nested if statements to improve clarity and prevent deeply indented, hard-to-read code.
- **No Nested switch or ternary Operators:** Never nest switch statements or ternary operators, as they lead to hard-to-follow logic. Use intermediate variables if necessary.
- **Limit Long Ternary Operations:** Avoid overly long ternary operations. Extract complex ternary conditions to well-named variables.
- **Avoid any Type:** Never use the any type in TypeScript. Always define precise types for variables and functions.
- **Prefer Simple, Pure Functions:** Write small, pure functions with a single responsibility. Functions should not have side effects.
- **Minimize Code Nesting:** Try to keep code nesting minimal for better readability and maintainability.
- **Avoid Magic Numbers:** Replace hardcoded numbers with constants that clearly express their meaning.
- **Centralize Constants:** Store constant values (strings, numbers, enums) in a central place, such as a constants file or an enum.
#### Naming Conventions:
- **Constants:** Use `ALLCAPS` (e.g., `MAX_LIMIT`, `API_URL`).
- **Variables:** Use `camelCase` (e.g., `userDetails`, `apiResponse`).
- **Files and Folders:** Use `kebab-case` for files and folder names (e.g., `user-profile.component.ts`, `auth-service.ts`).
- **Clarity in Naming:** Stick to established conventions and avoid ambiguous or misleading names. Names should convey the purpose clearly.
- **Commenting:** Provide brief comments for non-obvious variables or logic, explaining the "why" behind complex decisions or algorithms.
- **Avoid Redundancy:** Eliminate unnecessary or redundant words in names (e.g., `PaginationHelper` should just be `Pagination`).
- **Functions:** Name functions after the action they perform (e.g., `fetchData`, `updateUser`).
- **Variables:** Name variables using concise, meaningful nouns (e.g., `userList`, `totalCount`).
- **Classes:** Use singular, descriptive nouns for class names (e.g., `UserProfile`, `InvoiceManager`). Avoid acronyms unless it's an established convention.
### 4. Recommended Design Patterns
#### **Solid Priciples**
- **Single Responsibility Principle (SRP):** Each class or component should have one job or reason to change.
- **Open/Closed Principle (OCP):** Components should be open for extension but closed for modification. This allows for flexible future changes.
- **Liskov Substitution Principle (LSP):** You should be able to replace a class with its subclass without affecting the program's behavior.
- **Interface Segregation Principle (ISP):** Clients should not be forced to implement interfaces they don’t use. Keep interfaces focused and small.
- **Dependency Inversion Principle (DIP):** High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).
#### **Behavioral Design Patterns**
- **Memento**
- **State**✅
- **Iterator**✅
- **Strategy**✅
- **Chain of Responsibility**✅
- **Template Method**✅
- **Command**✅
- **Mediator**
- **Observer**
- **Visitor**
#### **Structural Design Patterns**
- **Composite**
- **Adapter**
- **Decorator**
- **Facade**✅
- **Flyweight**
- **Brdige**
- **Proxy**
#### **Creational Design Patterns**✅
- **Prototype**✅
- **Singleton**✅
- **Factory Method**✅
- **Abstract Factory**✅
- **Builder**✅
#### Other Principles:
- **KISS (Keep It Simple, Stupid)** ✅
Always strive for simplicity. Avoid over-engineering solutions.
- **DRY (Don’t Repeat Yourself)** ✅
Avoid repeating logic. Extract common functionality into reusable functions or services. - Don't repeat yourself
### 5. Additional Best Practices
- **Testing:** Always write unit tests for your services, components, and use cases. Ensure your code is testable by following principles like Dependency Injection and separation of concerns.
- **Error Boundaries:** Use Angular's built-in error handling mechanisms to catch and log errors appropriately.
- **Modularization:** Split your application into smaller, feature-based modules to improve scalability and maintainability.
- **Documentation:** Use clear and concise documentation for complex modules, functions, or components. Aim for clarity but avoid over-documenting obvious parts of the code which most of it should be.