Important Notice

While using ChatGPT, don't copy-paste exact content. Read carefully and make necessary changes.

1. Introduction

This document outlines the coding standards and best practices for developing Angular applications within SIONIQ. It covers aspects such as component architecture, services, performance, testing, and styling. Following these standards ensures consistency, maintainability, and high-quality code.

2. Project Structure

                 sioniqai/
                 ├── e2e/                     # End-to-end testing folder
                 │   ├── src/                 
                 │   ├── app.e2e-spec.ts      # E2E test specification
                 │   └── app.po.ts            # Page object model for E2E tests
                 ├── node_modules/             # Dependencies installed by npm
                 ├── src/                      # Main source code folder
                 │   ├── app/                  # Core application code
                 │   │   ├── core/             # Core module for app-wide services, utilities, etc.
                 │   │   ├── sioniqai/         # SioniqAI feature modules (previously features)
                 │   │   │   ├── admin/        # Admin module
                 │   │   │   ├── inventory/    # Inventory module
                 │   │   │   ├── master/       # Master data module
                 │   │   │   │   ├── country/  # Country form
                 │   │   │   │   │   ├── model/   # Models for country
                 │   │   │   │   │   │   ├── add-country.model.ts           # Model for add country
                 │   │   │   │   │   │   ├── view-country.model.ts          # Model for view country
                 │   │   │   │   │   │   ├── edit-country.model.ts          # Model for edit country
                 │   │   │   │   │   │   └── delete-country.model.ts        # Model for delete country
                 │   │   │   │   │   ├── add-country/             # add Country component
                 │   │   │   │   │   │   ├── add-country.component.ts
                 │   │   │   │   │   │   ├── add-country.component.html
                 │   │   │   │   │   │   ├── add-country.component.spec.ts
                 │   │   │   │   │   │   └── add-country.component.css
                 │   │   │   │   │   ├── view-country/            # View Country component
                 │   │   │   │   │   │   ├── view-country.component.ts
                 │   │   │   │   │   │   ├── view-country.component.html
                 │   │   │   │   │   │   ├── view-country.component.spec.ts
                 │   │   │   │   │   │   └── view-country.component.css
                 │   │   │   │   │   ├── edit-country/            # Edit Country component
                 │   │   │   │   │   │   ├── edit-country.component.ts
                 │   │   │   │   │   │   ├── edit-country.component.html
                 │   │   │   │   │   │   ├── edit-country.component.spec.ts
                 │   │   │   │   │   │   └── edit-country.component.css
                 │   │   │   │   │   ├── delete-country/          # Delete Country component
                 │   │   │   │   │   │   ├── delete-country.component.ts
                 │   │   │   │   │   │   ├── delete-country.component.html
                 │   │   │   │   │   │   ├── delete-country.component.spec.ts
                 │   │   │   │   │   │   └── delete-country.component.css
                 │   │   │   │   │   ├── services/       # Services for country
                 │   │   │   │   │   │   ├── country.service.ts           # Service for country operations
                 │   │   │   │   │   │   └── country.service.spec.ts      # Unit tests for country service
                 │   │   │   │   ├── state/    # State form (added following the country structure)
                 │   │   │   │   │   ├── model/   # Models for state
                 │   │   │   │   │   │   ├── add-state.model.ts           # Model for add state
                 │   │   │   │   │   │   ├── view-state.model.ts          # Model for view state
                 │   │   │   │   │   │   ├── edit-state.model.ts          # Model for edit state
                 │   │   │   │   │   │   └── delete-state.model.ts        # Model for delete state
                 │   │   │   │   │   ├── add-state/             # add State component
                 │   │   │   │   │   │   ├── add-state.component.ts
                 │   │   │   │   │   │   ├── add-state.component.html
                 │   │   │   │   │   │   ├── add-state.component.spec.ts
                 │   │   │   │   │   │   └── add-state.component.css
                 │   │   │   │   │   ├── view-state/            # View State component
                 │   │   │   │   │   │   ├── view-state.component.ts
                 │   │   │   │   │   │   ├── view-state.component.html
                 │   │   │   │   │   │   ├── view-state.component.spec.ts
                 │   │   │   │   │   │   └── view-state.component.css
                 │   │   │   │   │   ├── edit-state/            # Edit State component
                 │   │   │   │   │   │   ├── edit-state.component.ts
                 │   │   │   │   │   │   ├── edit-state.component.html
                 │   │   │   │   │   │   ├── edit-state.component.spec.ts
                 │   │   │   │   │   │   └── edit-state.component.css
                 │   │   │   │   │   ├── delete-state/          # Delete State component
                 │   │   │   │   │   │   ├── delete-state.component.ts
                 │   │   │   │   │   │   ├── delete-state.component.html
                 │   │   │   │   │   │   ├── delete-state.component.spec.ts
                 │   │   │   │   │   │   └── delete-state.component.css
                 │   │   │   │   │   ├── services/       # Services for state
                 │   │   │   │   │   │   ├── state.service.ts           # Service for state operations
                 │   │   │   │   │   │   └── state.service.spec.ts      # Unit tests for state service
                 │   │   │   ├── pos/          # Point of Sale module
                 │   │   │   ├── b2b/          # Business-to-Business module
                 │   │   │   ├── manufacture/  # Manufacturing module
                 │   │   │   ├── scheme/       # Scheme management module
                 │   │   │   ├── procurement/  # Procurement module
                 │   │   ├── app.module.ts     # Root module
                 │   │   ├── app.component.ts  # Root component
                 │   │   ├── app.component.html
                 │   │   └── app.component.scss
                 │   ├── assets/               # Static assets (images, fonts, etc.)
                 │   ├── environments/         # Environment variables for build configurations
                 │   │   ├── environment.ts    # Development environment
                 │   │   └── environment.prod.ts # Production environment
                 │   ├── favicon.ico           # Icon for the application
                 │   ├── index.html            # Main HTML page
                 │   ├── main.ts               # Main entry point for the application
                 │   ├── polyfills.ts          # Polyfills for browser compatibility
                 │   ├── styles.scss           # Global styles
                 │   └── tsconfig.app.json     # TypeScript configuration for the app
                 ├── angular.json              # Angular CLI configuration
                 ├── package.json              # Project dependencies and scripts
                 ├── tsconfig.json             # Global TypeScript configuration
                 ├── tsconfig.spec.json        # TypeScript configuration for tests
                 ├── tslint.json               # Linter configuration
                 ├── karma.conf.js             # Karma test runner configuration
                 └── README.md                 # Project documentation
            

3. Component Naming and Standards

3.1 Component Naming Convention

Components should follow a clear and consistent naming convention. The file and class names should reflect the component's purpose and be named in PascalCase format. Always include the suffix Component for clarity. For example:

Purpose File Name Class Name
Adding a country add-country.component.ts AddCountryComponent
Viewing a country view-country.component.ts ViewCountryComponent
Editing a country edit-country.component.ts EditCountryComponent
Deleting a country delete-country.component.ts DeleteCountryComponent
3.2 Component Structure and Organization

Each component should follow Angular's recommended file structure. Use separate files for the component's logic, HTML template, and CSS styling. Organize the files in a folder named after the component for easy navigation and maintainability.

3.3 Selector Naming

The component selector should use kebab-case (lowercase letters with hyphens). Prefix the selector with a short, unique identifier to avoid conflicts with standard HTML elements or other libraries.

Example:

Component Selector
AddCountryComponent app-add-country
ViewCountryComponent app-view-country
EditCountryComponent app-edit-country
DeleteCountryComponent app-delete-country
3.4 Properties and Subscriptions

In Angular components, all properties should be strongly typed to improve readability, maintainability, and type safety. Follow naming conventions to easily distinguish between public and private properties, where:

When using subscriptions, ensure they are properly managed and unsubscribed in ngOnDestroy to avoid memory leaks. This is especially important for components that interact with services, perform API requests, or listen to route changes.

Example Properties and Subscriptions

/**
 * Category ID from route parameters, representing the category being edited.
 * @type {string | null}
 */
id: string | null = null;

/**
 * Category data model that holds the fetched category details for editing.
 * @type {Category | undefined}
 */
category?: Category;

/**
 * Subscription for route parameter changes to track category ID changes.
 * @type {Subscription | undefined}
 */
private _paramsSubscription?: Subscription;

/**
 * Subscription for API requests to update a category.
 * @type {Subscription | undefined}
 */
private _editCategorySubscription?: Subscription;

/**
 * Subscription for API requests to delete a category.
 * @type {Subscription | undefined}
 */
private _deleteCategorySubscription?: Subscription;
            
Common Naming Conventions for Properties and Subscriptions

Consistent naming conventions help differentiate properties, particularly when handling observables and managing state.

1. Naming Conventions for Properties
2. Naming Conventions for Subscriptions
3.5 Dependency Injection in Constructor

Use dependency injection for service classes and other dependencies. Each injected property should have a descriptive type and be marked as private if it’s not used outside the class.

Example:


 constructor(private categoryService: CategoryService, private router: Router) {
     this.model = { name: '', urlHandle: '' };
 }
            
3.6 Method Naming and Usage

Methods should follow camelCase naming conventions, where the first letter of each word is capitalized except for the initial word. Method names should clearly describe their functionality. Standard Angular lifecycle hooks such as ngOnInit and ngOnDestroy should be used as defined, while custom methods should follow specific prefixes based on their actions.

Here are some conventions for common method types:

Example:


/**
 * Initializes the component.
 * Called once after the component's data-bound properties have been initialized.
 */
ngOnInit(): void {
    // Logic to initialize component, e.g., fetching initial data
    this.fetchCategories();
}

/**
 * Handles the form submission for adding a category.
 * Subscribes to the addCategory service and navigates to the 
 * categories page upon a successful response.
 *
 * @returns {void} 
 */
onFormSubmit(): void {
    this.addCategorySubscription = this.categoryService.addCategory(this.model)
      .subscribe({
        next: (response) => {
          this.router.navigateByUrl('/admin/categories');
        }
      });
}

/**
 * Handles the deletion of a category.
 * Subscribes to the deleteCategory service and refreshes the list 
 * upon successful deletion.
 *
 * @param {string} id - The ID of the category to delete.
 * @returns {void}
 */
onDelete(id: string): void {
    this.categoryService.deleteCategory(id).subscribe({
        next: () => this.fetchCategories()  // Refreshes the list after deletion
    });
}

/**
 * Cleans up subscriptions and other resources when the component is destroyed.
 * This is a necessary cleanup step to prevent memory leaks.
 *
 * @returns {void}
 */
ngOnDestroy(): void {
    this.addCategorySubscription?.unsubscribe();
}
            
Explanation of Method Examples

Following these naming conventions and structuring methods with clear, descriptive names ensures the component code remains readable, maintainable, and aligns with Angular’s standards.

3.7 Lifecycle Hooks

Utilize Angular lifecycle hooks such as ngOnInit for initialization and ngOnDestroy for cleanup. Make sure to unsubscribe from observables within ngOnDestroy to prevent memory leaks.

Example:


/**
 * Cleans up subscriptions when the component is destroyed.
 *
 * @returns {void}
 */
ngOnDestroy(): void {
    this.addCategorySubscription?.unsubscribe();
}
            

4. Angular Service Standards

4.1 Service Responsibilities

Each service should have a single responsibility. For example, a UserService should be responsible only for user-related operations, like login, registration, and profile management. A ProductService should handle product-specific operations, such as creating, updating, and retrieving products.

4.2 Service Naming

The service file should be named according to the following convention: <feature>.service.ts. The name of the service should clearly reflect its purpose and functionality. For example, a service handling categories should be named category.service.ts, and a service handling countries should be named country.service.ts.

4.3 Service Class Naming

The service class should be named using PascalCase (i.e., starting with an uppercase letter and capitalizing each word that follows). For example, a service responsible for managing categories should be named CategoryService, and a service for managing countries should be named CountryService. Always include the suffix Service to indicate that the class provides services for business logic or data access.

4.4 Service File Naming

Service file names should follow the format: <feature>.service.ts. This helps to ensure consistency across the application. The file name should be in lowercase with hyphens separating words if needed.

Feature Service File Name Service Class Name Purpose
Category category.service.ts CategoryService Handles CRUD operations related to categories.
Country country.service.ts CountryService Handles operations related to countries, such as retrieving country information.
State state.service.ts StateService Manages state-related operations, such as fetching state data and updating state records.
Product product.service.ts ProductService Provides methods to add, update, delete, and fetch products.
Order order.service.ts OrderService Manages operations related to customer orders, including creation, modification, and cancellation.
User user.service.ts UserService Handles user-related tasks such as authentication, registration, and profile management.
4.5 Method Naming

Methods within the service should be named using camelCase and should clearly describe the action they perform. The names should follow standard CRUD operation patterns:

Method Naming Examples
Method Name Action
addCategory Adds a new category to the system.
getAllCategories Fetches all categories from the server.
getCountryById Fetches a specific country by its ID.
updateProduct Updates the details of an existing product.
deleteState Deletes a state from the system.
4.6 Service Injection

Services should be provided using Angular’s @Injectable decorator. The providedIn: 'root' option should be used for services that are required globally across the application. If you need the service to be scoped to a specific module or component, you can provide it locally within that module or component instead.


@Injectable({
  providedIn: 'root'
})
export class CategoryService { }
            
Service Injection Strategy Explanation
CategoryService @Injectable({ providedIn: 'root' }) Available globally across the application, accessible in any component, directive, or pipe.
UserService @Injectable({ providedIn: 'root' }) Available globally throughout the application for handling user-related operations.
OrderService @Injectable({ providedIn: 'root' }) Available globally to manage orders across the app.
StateService @Injectable({ providedIn: 'any' }) Scoped to a particular module for managing states in that module.
4.7 Use Observables for Asynchronous Operations

Always use Observable for handling asynchronous operations in services. For example, when making HTTP requests, the HttpClient methods return observables that can be subscribed to or piped with operators for data transformation and error handling.


getCategories(): Observable {
    return this.http.get(`${this.apiUrl}/categories`)
      .pipe(
        catchError(this.handleError)
      );
}
            
4.8 Error Handling

Proper error handling should be implemented in services, especially when making HTTP requests. Use RxJS operators like catchError to catch errors and return a user-friendly message or perform fallback logic.


private handleError(error: any): Observable {
    console.error('An error occurred:', error);
    return throwError(() => new Error('Something went wrong.'));
}
            
4.9 Avoid Logic in Components

Services should abstract business logic away from components. Components should handle presentation logic, and delegate business logic to services. This helps to keep components clean and improves reusability.


// Component
this.categoryService.getAllCategories().subscribe(data => {
    this.categories = data;
});
            
4.10 Use Dependency Injection Properly

Always use Angular's Dependency Injection (DI) to inject services into components. Avoid creating instances of services manually using new to ensure proper lifecycle management and singleton pattern.


            constructor(private categoryService: CategoryService) {}
            
4.11 Service Method Return Types

Always explicitly define the return types for service methods. This ensures better type safety and helps maintain cleaner, more predictable code.


getAllCategories(): Observable {
    return this.http.get(`${this.apiUrl}/categories`);
}
            
4.12 Avoid Nesting Observables

Avoid nesting observables within one another. Use operators like switchMap, mergeMap, etc., to flatten observables and avoid callback hell.


getCategory(id: string): Observable {
    return this.categoryService.getCategoryById(id).pipe(
        switchMap((category: Category) => this.productService.getProductsByCategory(category.id))
    );
}
            
4.13 State Management

If your application is large, consider using state management libraries like NgRx to handle complex state logic in a predictable manner. Services can interact with the state store or dispatch actions to update the state.

4.14 Keep Services Testable

Services should be easily testable. Write unit tests for services, especially those containing business logic. Use mocks or spies for dependencies and ensure that services return expected results.


it('should fetch all categories', () => {
    service.getAllCategories().subscribe(data => {
        expect(data.length).toBeGreaterThan(0);
    });
});
            
4.15 Avoid Using any Type

Avoid using the any type as much as possible. Always specify the return types and parameter types explicitly to improve type safety.

4.16 Service Interface or Model Contracts

Use interfaces for models or service responses. This ensures consistency in the data structure and better integration with TypeScript's type system.


export interface Category {
    id: string;
    name: string;
    description: string;
}

getCategories(): Observable {
    return this.http.get(`${this.apiUrl}/categories`);
}
            
4.17 Use Constants for API URLs

Store API URLs and other constants in environment configuration files (e.g., environment.ts) to avoid hardcoding them throughout your services. This makes it easier to update the URLs later and keeps your codebase clean.


const apiUrl = environment.apiBaseUrl;
            

5. Angular Model Standards

5.1 Model File Structure

Organize model files by functionality to improve code readability and maintainability. Each model should reside in a dedicated file, with the file name following a consistent pattern that reflects the model's purpose and actions.

Purpose Suggested File Name
Model for adding a new country add-country.model.ts
Model for viewing country data view-country.model.ts
Model for editing an existing country edit-country.model.ts
Model for deleting a country delete-country.model.ts
5.2 Model Class Naming Conventions

Model classes should be named using PascalCase and should clearly represent the model's functionality. The names should include an action prefix like Add, View, Edit, or Delete followed by the entity name. This naming convention improves readability and makes it clear what each model is used for.

Purpose Suggested Class Name
Model for adding a country AddCountry
Model for viewing country data ViewCountry
Model for editing an existing country EditCountry
Model for deleting a country DeleteCountry
5.3 Defining Class Properties

Each property in a model class should have an explicit type to maintain type safety. Use @type annotations to describe each property, providing context for its usage. Additionally, optional properties should be marked with a ? to indicate they are not required.

Example:


/**
 * Model for adding a new category.
 */
export class AddCategory {
    /**
     * The name of the category.
     * @type {string}
     */
    name: string;

    /**
     * A unique URL handle for the category, used for routing and SEO purposes.
     * @type {string}
     */
    urlHandle: string;

    /**
     * Optional description of the category.
     * @type {string | undefined}
     */
    description?: string;
}
            
Important Notes:

6. Component HTML Standards

6.1 File Naming and Structure

The naming convention for component HTML files should follow a consistent and clear pattern. This ensures easy identification of the component’s purpose and functionality. The component HTML file should be named using **kebab-case** format, which improves readability and helps avoid potential conflicts in case-sensitive file systems.

For example:

Component Name HTML File Name Description
AddCategoryComponent add-category.component.html HTML file for the AddCategoryComponent, handling category creation.
ViewCategoryComponent view-category.component.html HTML file for viewing a specific category's details.
EditCategoryComponent edit-category.component.html HTML file for editing a category's data.
DeleteCategoryComponent delete-category.component.html HTML file for deleting a category.

This consistent naming structure allows the development team to easily locate and maintain component files based on their names.

6.2 HTML Controls ID and Name Conventions

To maintain consistency and accessibility, the naming conventions for HTML control IDs and names should follow specific rules based on the type property of the control. These conventions ensure that the controls are identifiable, usable, and easily linked to the appropriate form fields or validation rules.

General Guidelines
ID and Name Naming Based on Control Type

Below are examples of how to name IDs and names based on the type of control, along with the type and corresponding CSS classes.

Control Type Type Attribute ID Naming Convention Name Naming Convention Example CSS Class
Text Input type="text" kebab-case, representing the field name kebab-case, matching the ID attribute for form submission id="first-name", name="first-name" class="form-control"
Password Input type="password" kebab-case, prefixed with "password" for clarity kebab-case, matching the ID attribute for form submission id="password", name="password" class="form-control"
Radio Button type="radio" kebab-case, with the radio group name kebab-case, with the radio group name id="gender-male", name="gender" class="form-check-input"
Checkbox type="checkbox" kebab-case, with the action or item kebab-case, matching the ID for form submission id="subscribe-newsletter", name="subscribe-newsletter" class="form-check-input"
Textarea type="textarea" kebab-case, based on the field description kebab-case, matching the ID for form submission id="user-feedback", name="user-feedback" class="form-control"
Dropdown (Select) type="select" kebab-case, representing the field name kebab-case, representing the field name id="country-select", name="country" class="form-select"

By adhering to these conventions, we ensure that the HTML controls are consistently named and accessible, making it easier to maintain and understand the forms in your application.

7. Routing Module Standards

Routing in Angular allows navigation between views or components within the application. Angular's RouterModule provides a powerful mechanism for routing and handling URLs. This section outlines the standards to follow when defining routes and setting up the routing module in an Angular application.

7.1 Defining Routes

In Angular, routes are defined in an array of objects, where each object specifies the URL path and the associated component. Each route object should include at least two properties: path and component.

Best Practices for Defining Routes

7.2 Routing Module Setup

The routing module is a feature module that imports RouterModule and defines the routes. It is typically set up in the app-routing.module.ts file.

7.3 Best Practices for Route Naming
7.4 Route Guards

Use route guards to control navigation based on certain conditions. For example, route guards can be used to check if a user is authenticated before allowing them to navigate to certain routes. Guards should be set up in the routing configuration and can be added using the canActivate or canLoad properties.


import { AuthGuard } from './guards/auth.guard';

const routes: Routes = [
  {
    path: 'admin/categories',
    component: CategoryListComponent,
    canActivate: [AuthGuard]  
  }
];
           
7.5 Lazy Loading Routes

To improve performance, use lazy loading for feature modules. Lazy loading allows Angular to load feature modules only when needed. This can greatly improve the initial load time of your application.


const routes: Routes = [
  {
    path: 'admin/categories',
    loadChildren: () => import('./features/category/category.module').then(m => m.CategoryModule) 
  }
];