My Blogs:

Highlights of my work

Dynamic Tables with FormArrays in Angular

+

Introduction

In modern web development, creating dynamic and responsive user interfaces is crucial for enhancing user experience. Angular, a powerful framework for building single page applications, offers robust tools to manage forms and user inputs effectively. One such feature is FormArray, which allows developers to create dynamic forms with multiple rows of input fields—ideal for scenarios like managing lists of items, user entries, or any repetitive data structures. In this article, we will explore how the implementation of FormArray in Angular creates dynamic rows in tables.

The requirement

Our application includes a dialog interface for creating an attending list for events. This dialog requires inputs for the attending list's ‘Name’ and ‘Description’. Beyond these fields, we needed to add an editable table where users can choose and define attributes, which will appear in the attending list as fields to be completed by each attendee. In this table, the user should be able to dynamically add, edit, and remove rows, with each row representing a customizable attribute such as Attending, Kids, Plus One, etc.

How was implemented

  1. For start, we declared and initialized a FormArray inside a FormGroup, which will be linked to the table rows. This allows us to dynamically add and remove rows from the table, with each row corresponding to a form group in the FormArray.
     this.attendingListForm = new Formgroup({
          Name: new FormControl('', [Validators.required, noWhitespaceValidator]),
          Description: new FormControl('', [Validators.maxLength(500), noWhitespaceValidator]),
          Attributes: new FormArrauy([]),
      }); 
  2. We implemented a method loadAttributes which is called after the attributes data is fetched from the backend. This method iterates over each record from the backend, creates a new FormGroup for each, and then pushes it into the Attributes FormArray.
  3. Now that Attributes FormArray is updated with the backend data, we bind the table’s data source to the FormArray so that the HTML table is directly linked to the form, and any changes made to the form automatically update the data source.
     this.dataSource.data = this.attributes.value; 
  4. In the HTML, for each column in the table, the formGroupName directive is applied, with the index of the form group as its value:
     <div [formGroupName]="element.Index" id="new-attribute"> 
    By giving the index as value, the input field will be linked to its corresponding form group in the FormArray.
  5. We used conditional rendering for each column to either display an input field or show the record’s value as plain text, depending on the isEdit property of each attribute. If the isEdit property is true, the input fields are displayed. IfisEdit is false, the record’s values are displayed as plain text, preventing any changes.
  6. Create new row.
    When the user clicks Add button, an empty new FormGroup is created and added to the FormArray. This new form group is initialized with the isEdit property set to ‘true’, which means that the input fields will be displayed, allowing the user to fill in the necessary data.

    After entering the data, the user has two options: to initiate the creation of a new item and another to discard any changes made. If the user decides not to keep the new row, they can click the ‘Remove’ icon. This action triggers the removeAttributemethod, which removes the corresponding form group from the Attributes FormArray, using removeAt() method provided by the FormArray. This method requires the index of the form group as a parameter.

    this.attributes.removeAt(row.Index!);
    If the user clicks on ‘Save’ icon, the saveAttribute method is called. In this case, the form group remains part of the FormArray and the data it is send to the backend to be saved in the database.
  7. Edit row.
    When the user clicks the ‘Edit’ icon, the isEdit property for that row is set to true and the row becomes editable. After making the changes, the user has two options: save or discard the changes.

  8. Delete row.
    When the user clicks the ‘Delete’ icon, the deleteAttribute method is called. This method removes the corresponding form group from the Attributes FormArray using the index of the row and it also triggers the logic to delete the record from the database.
    For each one of the actions - create, remove, edit, delete - the data source is updated with the values from the FormArray to ensure that the displayed information is synchronized with the form.

Conclusion

In conclusion, we chose to use FormArray for this requirement because it allows us to dynamically manage a table of attributes. By leveraging FormArray, we can easily add, remove, or manipulate form controls dynamically, ensuring that the application remains flexible and responsive to user actions.


Electron Integration with Vite + React

+

Introduction

This project uses Electron in combination with Vite and React to turn a modern web app into a cross-platform desktop application.

How Electron Works

Electron is a framework that allows you to create desktop applications using web technologies like HTML, CSS, and JavaScript. It has two main processes:

  1. Main Process: Runs the Electron app, creates windows, handles OS-level interactions.
  2. Renderer Process: Runs your frontend (the React app in this case), similar to a browser tab.

In this project

  • The React app is bundled with Vite.
  • Electron loads the final dist/index.html into a desktop window.
  • We use electron-builder to package the app and generate an .exe.

Vite + React Setup (vite.config.ts)


import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  base: './',
  plugins: [react()],
  build: {
    outDir: 'dist',
  },
});
  • base: './':Ensures relative paths in index.html, important for Electron where the app isn’t served from a web server.
  • plugins: [react()]: Adds React support to Vite.
  • build.outDir: Tells Vite to place the production build in the dist folder (used by Electron to load the UI).

Electron Main Process (electron/main.ts)


import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

function createWindow() {
  const win = new BrowserWindow({
    width: 1000,
    height: 800,
    icon: path.join(__dirname, '../public/icon.ico'),
    webPreferences: {
      contextIsolation: true,
    },
  });

  win.loadFile(path.join(__dirname, '../dist/index.html'));
}

app.whenReady().then(() => {
  createWindow();
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});
What it does:
  • Creates a window and loads your index.html from Vite.
  • The icon appears in the window title bar and taskbar.
  • contextIsolation: true: Security feature recommended by Electron.

TypeScript Config (tsconfig.electron.json)


{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ES2020",
    "moduleResolution": "node",
    "outDir": "dist-electron",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["electron/**/*"]
}
What it does:
  • Compiles your Electron source files (e.g., main.ts) to JavaScript in the dist electron/ folder.
  • Ensures modern JS output and correct module handling.
  • include: Points to your Electron source directory.

Electron Builder Config (in package.json)


"build": {
  "appId": "com.crossway.viewer",
  "productName": "Crossway Diagram Viewer",
  "files": [
    "dist/**/*",
    "dist-electron/**/*"
  ],
  "extraMetadata": {
    "main": "dist-electron/main.js"
  },
  "win": {
    "target": "nsis",
    "icon": "./public/icon.ico"
  }
}
What it does:
  • files: Includes built frontend and Electron files.
  • main: Defines the Electron entry point.
  • win.icon: Custom app icon for the Windows .exe.
  • target: nsis: Generates a Windows installer.

Usage

  1. Run npm install
  2. Run npm run electron:dev to build and launch the app in dev mode
  3. Run npm run make to generate the installable .exe

Notes


  • The icon should be 256x256 or larger for proper Windows usage.
  • dist/ and dist-electron/ are generated during the build.

Rich Text Editor – Angular Editor Kolkov

+

Introduction

In the evolving landscape of web development, rich text editors play a vital role in content creation. Angular Editor Kolkov is a powerful, Angular-specific tool offering a clean UI and strong integration features. With its robust features, user friendly interface, and seamless integration capabilities, Kolkov offers developers an efficient way to implement rich text editing functionality.

Requirement

In this application we have a dialog interface for technical questions. This dialog includes mandatory inputs for the question title and description and some optional inputs for the technology in question and attachments. For the ‘Description’ input we had to make a rich text editor where the user could add images, format the text and so on.

Implementation Steps

  1. Install with npm install @kolkov/angular-editor --save
  2. After a successful installation, we integrated the editor into the html file. One important property is the variable editorConfig from [config].
  3. <angular-editor>
      formControlName="Description"
      [config]="editorConfig"
      [ngClass]="{
        'error-editor':
          questionForm.get('Description')?.touched &&
          questionForm.get('Description')?.hasError('required')
      }">
    </angular-editor>
  4. Next, we defined the variable within the TypeScript file with values for height, placeholder, hidden buttons and so on.
  5. editorConfig: AngularEditorConfig = {
        editable: true,
        spellcheck: true,
        height: 200px,
        minHeight: 100px,
        placeholder: 'Description *',
        translate: no,
        defaultParagraphSeparator: 'p',
        defaultFontName: 'Arial',
        toolbarHiddenButtons: [['backgroundColor', 'toggleEditorMode']]
    }
  6. Our primary focus was the feature that allows images inside the text. In this editor the image is added as a clob inside the text. Since the text containing clobs can be too large to send to back end we created a way to save the images as separate attachments. Once an image is saved as an attachment, the clob in the text is replaced with a reference to it (in our case image-1730207134000.jpeg, where the number is the time zone) and then sent to back end. On read, we get the attachments and map them to replace the references in text and display the images through the text.
  7. Most of the work is carried out by the function that saves the technical question. We started by extracting the images from the description text using a regex. They are saved in variableimagesDescription and the regex used is saved inextractImageContent.
  8. this.imageDescription = questionData.Description.match(extractImagContent);
  9. After that, we call getImageOrVideo function for both create and update cases. This retrieves the images with the html tags <img src=…> not just the clob and filters out any video paths, as the editor supports videos that do not require any changes.
  10. private getImageOrVideo(description: string, regex: any): void {
        this.images = description.match(regex);
        if (description.includes('<a href=')) {
          this.images = this.images?.filter((file: any) => !file.includes('http'));
          this.imagesDescription = this.imagesDescription?.filter(
            (file: any) => !file.includes('http')
          );
        }
      }
  11. For the create scenario, we just iterate through the images extracted and create the attachments. For each image we replace it in the description with an image icon and the file name.
  12. questionData.Description = questionData.Description.replace(
        image,
        '<img src=${image_icon_path}/>(' + file.Name + ')'
    );
  13. For the update we must do more actions. After getImageOrVideo function, we replace the images extracted with its references for the before description, just as we do for the create case. Next, we create the new attachments needed from the description and we update the current description to replace all the clobs. This way we can safely send to back end.
  14. this.questionFile?.forEach(file => {
      questionData.Description = questionData.Description.replace(
        `<img src="${file.CLOB}">`,
        `<img src='${image_icon_path}'/> (${file.Name})`
      );
    });
  15. In case the description is not updated, only the other fields, we still need to replace the clobs with the corresponding references to ensure the data can be properly sent to the backend.
  16. For when we need to display the description, we read it from the back end and when we retrieve the attachments, we replace the reference with its attachment clob. Since we allow users to upload separate attachments for a question, we added a Boolean field ‘ImageDescription’ to distinguish between those files and the images from the description.
  17. if (attachment.ImageDescriptio)
      this.question.Description = this.question.Description.replace(
        `<img src="${image_icon_path}"/>(${attachment.Name})`,
        `<img src='${attachment.CLOB}'/>`
      );
    
  18. Then, to show the description in the format made by the user (with bold, italics, colored text, etc.) we added the property [innerHTML] to the <div> element that displays the saved description. This ensures the text appears as intended, with all user-applied styles intact.
  19. <div
      [innerHtml]="qiestion.Description"
      class="question-decription"
    

Conclusion

In conclusion, integrating Angular Editor Kolkov into an Angular application significantly enhances the user experience by providing a rich text editor capable of handling complex content such as images and formatted text. The implementation process, from installation to configuration and customization, ensures a smooth and efficient way to handle dynamic content creation, especially in cases where rich formatting and media embedding are essential, as demonstrated with the technical question dialog in this case. The solution of separating images as attachments while maintaining the ability to display them within the text is a practical approach to managing large content, ensuring scalability, and optimizing data transfer.


Unit Testing in Angular

+

Introduction to Angular and the Importance of Unit Testing

Angular is a powerful framework used to build modern, feature-rich web applications. It uses TypeScript, an enhanced version of JavaScript that introduces static typing and other tools to make code more robust and maintainable.

Unit testing plays a key role in Angular development. It focuses on testing individual parts of an application—such as components, services, or functions—to confirm they behave as expected. This helps developers catch bugs early, ensures code reliability, and protects existing features when updates are made. Although it requires some initial setup, unit testing ultimately reduces manual testing and makes long-term maintenance more efficient.

Setting Up the Testing Environment

To write and run tests in Angular, two key tools are commonly used: Jasmine and Karma.

  1. Jasmine is a testing framework that offers the tools and structure needed to write unit tests. It helps organize test suites using describe blocks and defines specific test scenarios with it blocks. Its clear and concise syntax allows developers to easily create and read test cases, making the intent of each test obvious.
  2. Karma is a test runner created by the Angular team that automates the execution of tests whenever the codebase is modified. This automation removes the need for developers to manually rerun tests after each change. The testing behavior can be customized using the karma.conf.js file, where options like target browsers, file inclusions/exclusions, and other settings can be defined to match the project's requirements.

When starting a new Angular project, the Angular CLI automatically includes Jasmine and Karma as part of the default testing setup. For existing projects, running ng add @angular/cli installs any missing testing dependencies, ensuring the project is properly equipped for unit testing.

After the initial setup, developers can further customize the environment through the karma.conf.js file. Once everything is configured, running ng test will start Karma, execute the tests, and show the results in real time.

Structure of an Angular Unit Test File

A typical test file in Angular is structured into several key sections, each serving a specific purpose in defining and organizing the tests:

  1. TestBed is Angular’s primary utility for configuring and initializing the testing environment. It sets up everything required to test a specific component or service by supplying the needed dependencies. This is done using theTestBed.configureTestingModule method, where you define the components, services, and modules involved in the test. This setup is crucial because components and services often rely on other parts of the application to function properly.
      await Testbed.configureTestingModule({
        imports: [MatmenuModule, MatDatepickerModule, SharedTestingModule],
        declarations: [ActionsBarComponent, HasAuthorityDirective],
        providers: [DatePipe],
      }).compileComponents(); 
    Within this configuration, the imports, declarations, and providers sections are used to include any necessary modules, components, or services that the tested component depends on. This ensures the testing environment closely mirrors the actual application setup and allows the tests to run smoothly.
  2. beforeEach/afterEach – Hooks for setting up and tearing down test state before and after each test case.
  3. Fixture – is a testing utility in Angular that provides a controlled environment for testing a component. It gives direct access to both the component’s instance and its rendered DOM, allowing interaction with and inspection of the component’s internal state and template. The fixture also handles change detection, ensuring the component updates in response to data changes—just like it would in a live Angular application. This makes it easier to test dynamic behavior and user interactions in real time.
      beforeEach(() => {
        fixture = TextBed.createComponent(FeedbackDialogComponent);
        component = fixture.componentInstance
        fixture.detectChanges();
      }); 
  4. It blockThis is where individual test cases are written. Each itblock contains a specific scenario or behavior to test, typically including one or more expectations that validate whether the code works as intended. The description passed to the it function should clearly communicate what the test is verifying, making it easy to understand the purpose of the test at a glance.
      it('should create', () => {
        expect(component).toBeTruthy();
      }); 
    In the following example, the it block verifies that the component has been successfully created by checking if it is truthy—meaning it exists and has been properly initialized:

    • toBe: checks for strict equality (byreference).
    • toEqual: checks for deep equality (by content).
    • toBeTruthy/toBeFalsy: Verifies if a value is true or false, such as if it exists or is null..
    • toContain: Checks if an array or string contains a specific value.
  5. Spies are utilities used in testing to monitor and track interactions with functions or methods. They’re especially helpful when testing components that rely on services or external functions. With spies, developers can verify whether a specific function was called, how many times it was invoked, and what arguments were passed to it. This allows for precise testing of interactions and behaviors without relying on the actual implementation of the function.
      it('should call "read" method with the corect parameters', () => {
        const questionId = '1';
        const resourceUrl = 'Question';
    
        service.readAnswerByQuestionId(questionId).subscribe();
        expect(readSpy).toHaveBeenCalledWith(resourceUrl, questionId);
      }); 
    In the example below, the test checks whether the readAnswerByQuestionId method calls the read function with the correct parameters: The line expect(readSpy).toHaveBeenCalledWith(...) uses a spy to verify that the read method was called with the expected arguments. If the method is called correctly, the test will pass; if not, it will fail—indicating that the component or service isn't behaving as intended.

Conclusion

Adopting proper unit testing practices in Angular ensures that components, services, and modules behave as intended. Well-written tests provide a safety net, allowing developers to maintain and scale their applications with confidence. By identifying issues early and promoting reliable code, unit testing plays a vital role in building stable and maintainable Angular projects.