Tuesday, September 23, 2025

Building a Simple C++ Method Extractor in C#

 

Extracting C++ Method Bodies with C#

When working with large C++ projects, sometimes all you need is a quick way to extract specific method bodies for debugging, refactoring, or documentation. Instead of building a full-fledged parser, you can rely on C# with regex and brace matching to create a lightweight and practical utility.

In this blog, we’ll walk through a step-by-step implementation of a tool that extracts method bodies from .cpp files. The process involves a simple main program, a model class, and the core extractor that handles regex parsing and safe brace matching.

Step 1 — Main Program (Program.cs)

The entry point initializes the extractor with a .cpp file path and prints out the results:

class Program
{
    static void Main(String[] args)
    {
        var methodBody = CppRegexExtractor.ExtractMethodsBodyWithRegex(@"C:\Temp\Dummy!.cpp");
        Console.WriteLine(methodBody);
        Console.ReadLine();
    }
}

This ensures the tool can read a given C++ source file and display the extracted method bodies.

Step 2 — Model Class (ProgramBody.cs)

We define a simple model to store each method’s name (Key) and body content (Content):

using System;

namespace ExtractCppCode
{
    public class ProgramBody
    {
        public string Key { get; set; }
        public string Content { get; set; }
    }
}

This acts as a container for extracted results, making it easier to handle multiple methods.

Step 3 — Core Extractor (CppRegexExtractor.cs)

Here’s where the real logic lives. The extractor uses regex to detect method signatures, then employs a brace matching algorithm to safely capture the full method body (ignoring braces inside strings or comments).

Key steps include:

  1. Regex pattern to detect C++ method signatures.
  2. Brace matching to find the correct closing brace.
  3. Optional sub-method detection to include methods invoked inside a target method (like SaveProduct).
using ExtractCppCode;
using System.Text;
using System.Text.RegularExpressions;

public static class CppRegexExtractor
{
    public static string ExtractMethodsBodyWithRegex(string cppFilePath)
    {
        if (!File.Exists(cppFilePath))
        {
            Console.WriteLine($"Error: File not found at {cppFilePath}");
            return "";
        }

        Console.WriteLine("Start Extracting Methods..........");

        List<ProgramBody> lines = new List<ProgramBody>();
        List<ProgramBody> subLines = new List<ProgramBody>();
        StringBuilder builder = new StringBuilder();

        string cppContent = File.ReadAllText(cppFilePath);

        // Regex for method signatures
        string pattern = @"(?<retType>[\w\s\*&<>:]+)\s+(?<className>[\w:]+::)?(?<methodName>\w+)\s*\((?<params>.*?)\)\s*\{";
        Regex regex = new Regex(pattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture);
        MatchCollection matches = regex.Matches(cppContent);

        foreach (Match match in matches)
        {
            string methodName = match.Groups["methodName"].Value;
            if (methodName == "if") continue; // skip keywords

            int bodyStartIdx = match.Index + match.Length - 1;
            int bodyEndIdx = FindMatchingBrace(cppContent, bodyStartIdx);

            string result = cppContent.Substring(match.Index, bodyEndIdx - match.Index + 1);
            lines.Add(new ProgramBody { Key = methodName, Content = result });
        }

        // Extract SaveProduct + its submethods
        var selectedMethod = lines.Find(x => x.Key == "PreSaveUpdate");
        if (selectedMethod != null)
        {
            builder.Append(selectedMethod.Content);

            string subPattern = @"\b(?<methodName>\w+)\s*\((?<arguments>[^)]*)\)";
            MatchCollection subMatches = Regex.Matches(selectedMethod.Content, subPattern);

            foreach (Match subMatch in subMatches)
            {
                string subMethodName = subMatch.Groups["methodName"].Value.Trim();
                if (subMethodName == selectedMethod.Key || subMethodName == "if") continue;

                var subItem = lines.Find(y => y.Key == subMethodName);
                if (subItem != null)
                {
                    subLines.Add(new ProgramBody
                    {
                        Key = subMethodName,
                        Content = subItem.Content
                    });
                }
            }

            foreach (var line in subLines)
            {
                builder.Append(line.Content);
            }
        }

        Console.WriteLine("Extracting Methods Completed..........");
        return builder.ToString();
    }

    // Finds the matching closing brace
    private static int FindMatchingBrace(string source, int openingBraceIdx)
    {
        if (source[openingBraceIdx] != '{')
            throw new ArgumentException("openingBraceIdx must point at a '{' character.");

        int depth = 0;
        bool inSingleLineComment = false;
        bool inMultiLineComment = false;
        bool inString = false;
        bool inChar = false;
        bool escaped = false;

        for (int i = openingBraceIdx; i < source.Length; i++)
        {
            char c = source[i];

            if (escaped) { escaped = false; continue; }
            if (c == '\\' && (inString || inChar)) { escaped = true; continue; }

            if (inSingleLineComment) { if (c == '\n') inSingleLineComment = false; continue; }
            if (inMultiLineComment) { if (c == '*' && i + 1 < source.Length && source[i + 1] == '/') { inMultiLineComment = false; i++; } continue; }

            if (inString) { if (c == '"') inString = false; continue; }
            if (inChar) { if (c == '\'') inChar = false; continue; }

            if (c == '/' && i + 1 < source.Length)
            {
                char next = source[i + 1];
                if (next == '/') { inSingleLineComment = true; i++; continue; }
                if (next == '*') { inMultiLineComment = true; i++; continue; }
            }
            if (c == '"') { inString = true; continue; }
            if (c == '\'') { inChar = true; continue; }

            if (c == '{') depth++;
            else if (c == '}')
            {
                depth--;
                if (depth == 0) return i;
            }
        }

        throw new InvalidOperationException("Unbalanced braces detected.");
    }
}

Sample Input — Dummy.cpp

Here’s a test C++ file containing various methods, including comments and tricky braces inside strings:

#include <iostream>
#include <string>

// A helper method with braces inside strings and comments
int helper1(int x)
{
    // This brace } in a comment should be ignored
    std::string s = "example with brace } inside string";
    if (x > 0) {
        return x + 1;
    }
    return 0;
}

/* Multiline comment with { and } that should be ignored */

void helper2()
{
    for (int i = 0; i < 3; ++i)
    {
        std::cout << "loop " << i << std::endl;
    }
}

// The main method we want to extract
bool SaveProduct(int id)
{
    std::cout << "SaveProduct called" << std::endl;
    int r = helper1(id);
    if (r > 0)
    {
        helper2();
        return true;
    }
    return false;
}

// Another unrelated method
double unrelated(double a, double b)
{
    return a * b;
}

Expected Extracted Output

When the extractor runs, it captures SaveProduct along with its submethods helper1 and helper2:

bool SaveProduct(int id)
{
    std::cout << "SaveProduct called" << std::endl;
    int r = helper1(id);
    if (r > 0)
    {
        helper2();
        return true;
    }
    return false;
}

int helper1(int x)
{
    // This brace } in a comment should be ignored
    std::string s = "example with brace } inside string";
    if (x > 0) {
        return x + 1;
    }
    return 0;
}

void helper2()
{
    for (int i = 0; i < 3; ++i)
    {
        std::cout << "loop " << i << std::endl;
    }
}

Sunday, September 14, 2025

Building a ChatGPT Chatbot in ASP.NET WebForms

 

Build a Simple ChatGPT WebForm App in ASP.NET (C#)

If you want to integrate ChatGPT into your ASP.NET WebForms project, here’s a step-by-step guide with complete code. We’ll create a small app where users can type prompts, send them to the OpenAI API, and display the assistant’s response — just like a mini chat interface.

Step 1: Define Models (Models.cs)

Create a folder named Models and add a file called MoModels.cs..Models.cs

.
This file defines the data structures for requests and responses.
using System;

namespace ChatGPT.Models
{
    public class ChatMessage
    {
        public string Role { get; set; } = string.Empty;
        public string Content { get; set; } = string.Empty;
    }

    public class Usage
    {
        public int? Prompt_Tokens { get; set; }
        public int? Completion_Tokens { get; set; }
        public int? Total_Tokens { get; set; }
    }

    public class Choice
    {
        public int Index { get; set; }
        public ChatMessage? Message { get; set; }
        public string? Finish_Reason { get; set; }
    }

    public class ChatResponse
    {
        public string? Id { get; set; }
        public string? Object { get; set; }
        public int Created { get; set; }
        public string? Model { get; set; }
        public Choice[]? Choices { get; set; }
        public Usage? Usage { get; set; }
    }

    public class ChatRequest
    {
        public string model { get; set; } = string.Empty;
        public ChatMessage[]? messages { get; set; }
        public int? max_tokens { get; set; }
    }
}

Step 2: Create OpenAI Client (OpenAiClient.cs)

Now, add a Controller folder and create OpenAiClient.cs.
This class handles communication with the OpenAI API.

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using ChatGPT.Models;

namespace ChatGPT.Controller
{
    public class OpenAiClient : IDisposable
    {
        private readonly HttpClient _httpClient;
        private readonly string _apiKey;
        private bool _disposed;

        public OpenAiClient(string apiKey)
        {
            _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
        }

        public async Task<ChatMessage> GetChatCompletionAsync(List<ChatMessage> conversationHistory)
        {
            if (conversationHistory == null) throw new ArgumentNullException(nameof(conversationHistory));

            var requestData = new ChatRequest
            {
                model = "gpt-3.5-turbo",   // Change to your preferred model
                messages = conversationHistory.ToArray(),
                max_tokens = 150
            };

            var jsonPayload = JsonConvert.SerializeObject(requestData);
            var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
            var apiUrl = "https://api.openai.com/v1/chat/completions";

            try
            {
                HttpResponseMessage response = await _httpClient.PostAsync(apiUrl, content).ConfigureAwait(false);
                response.EnsureSuccessStatusCode();
                string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                var chatResponse = JsonConvert.DeserializeObject<ChatResponse>(responseBody);

                if (chatResponse?.Choices?.Length > 0)
                {
                    return chatResponse.Choices[0].Message ?? new ChatMessage { Role = "assistant", Content = string.Empty };
                }

                return new ChatMessage { Role = "assistant", Content = "No response received." };
            }
            catch (HttpRequestException ex)
            {
                return new ChatMessage { Role = "assistant", Content = $"Error calling OpenAI API: {ex.Message}" };
            }
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                _httpClient?.Dispose();
                _disposed = true;
            }
        }
    }
}

Step 3: Build the Frontend (Home.aspx)

Create a new WebForm page named Home.aspx.
This will provide the UI for sending prompts and displaying responses.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="ChatGPT.Home" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ChatGPT WebForm</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <table width="99%">
                <tr>
                    <td>
                        <asp:TextBox ID="PromptTextBox" runat="server" 
                                     TextMode="MultiLine" Rows="5" Width="400" style="width:100%;"></asp:TextBox>
                    </td>
                </tr>
                <tr>
                    <td>
                        <asp:Button ID="SendButton" runat="server" 
                                    Text="Get AI Response" OnClick="SendButton_Click" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <div id="chatWindow" runat="server" 
                             style="height: 400px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px;">
                            <!-- Chat history will appear here -->
                        </div>
                    </td>
                </tr>
            </table>
        </div>
    </form>
</body>
</html>

Step 4: Handle Backend Logic (Home.aspx.cs)

Finally, implement the backend logic in Home.aspx.cs.

using System;
using System.Collections.Generic;
using System.Web.UI;
using ChatGPT.Models;
using ChatGPT.Controller;

namespace ChatGPT
{
    public partial class Home : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected async void SendButton_Click(object sender, EventArgs e)
        {
            string apiKey = "YOUR_API_KEY";   // Replace with your API key

            using (var openAiClient = new OpenAiClient(apiKey))
            {
                if (ViewState["ConversationHistory"] == null)
                {
                    ViewState["ConversationHistory"] = new List<ChatMessage>();
                }

                var conversationHistory = (List<ChatMessage>)ViewState["ConversationHistory"];
                string promptText = PromptTextBox.Text;

                if (!string.IsNullOrWhiteSpace(promptText))
                {
                    conversationHistory.Add(new ChatMessage { Role = "user", Content = promptText });
                    var aiResponse = await openAiClient.GetChatCompletionAsync(conversationHistory);
                    conversationHistory.Add(aiResponse);

                    // Save state
                    ViewState["ConversationHistory"] = conversationHistory;

                    // Clear textbox
                    PromptTextBox.Text = string.Empty;

                    // Render chat
                    RenderChatWindow(conversationHistory);
                }
            }
        }

        private void RenderChatWindow(List<ChatMessage> conversationHistory)
        {
            chatWindow.InnerHtml = string.Empty;

            foreach (var message in conversationHistory)
            {
                string cssClass = (message.Role == "user") ? "user-message" : "assistant-message";
                chatWindow.InnerHtml += $"<div class=\"{cssClass}\"><b>{message.Role}:</b><pre>{System.Web.HttpUtility.HtmlEncode(message.Content)}</pre></div>";
            }
        }
    }
}

If you are using.net framework < 4.6 then use below method to call api


public string GetChatCompletion(string promptText, string selectedModel)
{
    var requestData = new ChatRequest
    {
        model = selectedModel,
        messages = new ChatMessage[]
        {
            new ChatMessage { role = "user", content = promptText }
        }
    };

    var jsonPayload = JsonConvert.SerializeObject(requestData);
    var apiUrl = "https://api.openai.com/v1/chat/completions";

    try
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(apiUrl);
        request.Method = "POST";
        request.ContentType = "application/json";
        request.Headers["Authorization"] = "Bearer " + _apiKey;

        byte[] bytes = Encoding.UTF8.GetBytes(jsonPayload);

        using (Stream stream = request.GetRequestStream())
        {
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();
        }

        WebResponse response = request.GetResponse();
        using (var reader = new StreamReader(response.GetResponseStream()))
        {
            var responseBody = reader.ReadToEnd();
            var chatResponse = JsonConvert.DeserializeObject<ChatResponse>(responseBody);

            if (chatResponse?.Choices?.Length > 0)
            {
                return pichatResponse.Choices[0].Message.Content.Trim();
            }
            return "No response received.";
        }
    }
    catch (HttpRequestException ex)
    {
        return $"Error calling OpenAI API: {ex.Message}";
    }
}






Friday, March 15, 2024

Schedular Services using Coravel in .net core

Simple example of how to schedule job using Coravel in .net core
Coravel gives you a zero-configuration queue that runs in-memory. This is useful to offload long-winded tasks to the background instead of making your users wait for their HTTP request to finish.

Step 1 : Create a console app named CorvelJob

Step 2 : Install Coravel, Microsoft.Extension.DependencyInjection & Microsoft.Extension.Hosting

Step 3 : Below code written in Program.cs will run in every 2 seconds

using Coravel;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

var builder = Host.CreateApplicationBuilder();

builder.Services.AddScheduler();

var app = builder.Build();

app.Services.UseScheduler(schedular =>
{
    schedular.ScheduleAsync(
        async () =>
        {
            await Task.Delay(20);
            Console.WriteLine("Schiedular Time : " + DateTime.Now);
        }).EverySeconds(2);


});
app.Run();

OutPut : 

Schiedular Time : 3/15/2024 3:02:40 PM
Schiedular Time : 3/15/2024 3:02:42 PM
Schiedular Time : 3/15/2024 3:02:44 PM

You can also configure above code that run only on weekend that to on Sunday

schedular.ScheduleAsync(
        async () =>
        {
            await Task.Delay(20);
            Console.WriteLine("Schiedular Time : " + DateTime.Now);
        }).Weekly().Weekend().Sunday().Once();

 

you can invoke you class file also. Let take an example of MailerTask which sends mail only on weekend

Create MailerTask.cs

using Coravel;
using CorvelJob;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

var builder = Host.CreateApplicationBuilder();

builder.Services.AddScheduler();
builder.Services.AddTransient<MailerTask>();
var app = builder.Build();
app.Services.UseScheduler(schedular =>
{
    schedular.Schedule<MailerTask>()
        .Weekly().Weekend().Sunday()
        .PreventOverlapping("identifier");


});
app.Run();

 

Output : 

info: CorvelJob.MailerTask[0]

   Mail sent @ 03/15/2024 15:13:20

info: CorvelJob.MailerTask[0]

   Mail sent @ 03/15/2024 15:15:20

Saturday, October 16, 2021

Compare validator using reactive forms in angular

 Comparing two input values using reactive forms in angular






Step 1 : Validation.ts


import { AbstractControl, ValidatorFn } from '@angular/forms';

export default class Validation {
  static match(controlName: string, checkControlName: string): ValidatorFn {
    return (controls: AbstractControl) => {
      const control = controls.get(controlName);
      const checkControl = controls.get(checkControlName);

      if (checkControl?.errors && !checkControl.errors.matching) {
        return null;
      }

      if (control?.value !== checkControl?.value) {
        controls.get(checkControlName)?.setErrors({ matching: true });
        return { matching: true };
      } else {
        return null;
      }
    };
  }
}

Step 2 : app-component.html

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <form (ngSubmit)="onSubmit()" [formGroup]="form" autocomplete="off">
        <div id="user-data" formGroupName="userData">
          <div class="form-group">
            <label for="username" class="text-warning">User Name</label>
            <input type="username" placeholder="User Name" name="username" formControlName="username"
              class="form-control" />
            <span class="text-danger"
              *ngIf="!(form.get('userData.username')?.valid) && form.get('userData.username')?.touched">Please enter
              user name</span>
          </div>
          <div class="form-group">
            <label for="email" class="text-warning">Email</label>
            <input type="text" placeholder="Email" name="email" class="form-control" formControlName="email" />
            <span class="text-danger"
              *ngIf="!(form.get('userData.email')?.valid) && form.get('userData.email')?.touched">Please enter valid
              email</span>
          </div>
        </div>
        <div class="form-group">
          <label for="password" class="text-warning">Password</label>
          <input type="password" class="form-control" name="password" formControlName="password"
            [ngClass]="{ 'is-invalid':  f.password.errors }"
            placeholder="Password" />
          <span class="text-danger" *ngIf="f.password.errors?.required && f.password.touched">Please enter
            password</span>
          <span class="text-danger" *ngIf="f.password.errors?.minlength && f.password.touched">Password must be 6
            characters
          </span>
        </div>
        <div class="form-group">
          <label for="confirmpassword" class="text-warning">Confirm Password</label>
          <input type="password" class="form-control" name="confirmpassword" formControlName="confirmpassword"
            [ngClass]="{ 'is-invalid':  f.confirmpassword.errors }"
            placeholder="Confirm Password" />
          <span class="text-danger" *ngIf="f.confirmpassword.errors?.required && f.confirmpassword.touched">Please enter
            confirm password</span>
          <span class="text-danger" *ngIf="f.confirmpassword.errors?.matching && f.confirmpassword.touched">Confirm
            Password does not match</span>
        </div>

        <h4 *ngIf="hobbies.length < 2; else t" class="text-warning">Your Hobby</h4>
        <ng-template #t>
          <h4 class="text-warning">Your Hobbies</h4>
        </ng-template>

        <div class="form-group" formArrayName="hobbies">
          <button type="button" class="btn btn-success" (click)="onAddingHobby()">Add Hobby</button>
          <div id="dynamicControls" class="form-group" *ngFor="let l of hobbies.controls; let i = index">
            <span class="col-xs-5">
              <input type="text" [placeholder]="'Hobby ' + (i + 1)" [formControlName]="i" class="form-control"></span>
            <span><button id="removeControl" type="button" class="btn btn-danger" (click)="onRemovingHobby(i)">X
              </button></span>
          </div>
        </div>
        <button type="submit" [disabled]="!this.form.valid" class="btn btn-primary">Submit</button>
      </form>
    </div>
  </div>
</div>


Step 3 : app-component.ts

import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import Validation from './utils/validation';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  excludedUserName: string[] = ['Test', 'test'];
  form!: FormGroup;

  constructor(private formBuilder: FormBuilder) { }
 
  ngOnInit(): void {
    this.form = this.formBuilder.group({
      'userData': new FormGroup({
        'email': new FormControl(null, [Validators.required, Validators.email]),
        'username': new FormControl(null, [Validators.required,
        this.excludedUserNames.bind(this)])
      }),
      'password': new FormControl(null, [Validators.required, Validators.minLength(6)]),
      'confirmpassword': new FormControl(null, Validators.required),
      'hobbies': new FormArray([])
    }, {
      validators: [Validation.match('password', 'confirmpassword')]
    });
  }

  onSubmit() {
    console.log(this.form);
  }

  onAddingHobby() {
    const control = new FormControl(null, Validators.required);
    this.hobbies.push(control);
  }
  onRemovingHobby(i: number) {
    this.hobbies.removeAt(i);
  }

  excludedUserNames(fromControl: FormControl): { [s: string]: boolean } | null {
    if (this.excludedUserName.indexOf(fromControl.value) !== -1) {
      return { 'excludedUserName': true };
    }
    return null;
  }

  get hobbies() {
    return this.form.get('hobbies') as FormArray;
  }
 
  get f(): { [key: string]: AbstractControl } {
    return this.form.controls;
  }
}

Friday, October 15, 2021

Adding dynamic controls using reactive forms in angular

Adding dynamic controls using reactive forms




In the above example we are capturing user name , email and his/her hobbies. User can add more than one hobby. So we add hobby control dynamically.

Step 1 : app-component.html

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <form (ngSubmit)="onSubmit()" [formGroup]="form" autocomplete="off">
        <div id="user-data" formGroupName="userData">
          <div class="form-group">
            <label for="username" class="text-warning">User Name</label>
            <input type="username" placeholder="User Name" name="username" formControlName="username"
              class="form-control" />
            <span class="text-danger"
              *ngIf="!(form.get('userData.username')?.valid) && form.get('userData.username')?.touched">Please enter
              user name</span>
          </div>
          <div class="form-group">
            <label for="email" class="text-warning">Email</label>
            <input type="text" placeholder="Email" name="email" class="form-control" formControlName="email" />
            <span class="text-danger"
              *ngIf="!(form.get('userData.email')?.valid) && form.get('userData.email')?.touched">Please enter valid
              email</span>
          </div>
        </div>
        <h4 *ngIf="hobbies.length < 2; else t" class="text-warning">Your Hobby</h4>
        <ng-template #t>
          <h4 class="text-warning">Your Hobbies</h4>
        </ng-template>

        <div class="form-group" formArrayName="hobbies">
          <button type="button" class="btn btn-success" (click)="onAddingHobby()">Add Hobby</button>
          <div id="dynamicControls" class="form-group" *ngFor="let hobby of hobbies.controls; let i = index">
            <span class="col-xs-5">
              <input type="text" [placeholder]="'Hobby ' + (i + 1)" [formControlName]="i" class="form-control"></span>
            <span><button id="removeControl" type="button" class="btn btn-danger" (click)="onRemovingHobby(i)">X
              </button></span>
          </div>
        </div>
        <button type="submit" [disabled]="!this.form.valid" class="btn btn-primary">Submit</button>
      </form>
    </div>
  </div>
</div>

Step 2 : app-component.ts

import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  excludedUserName: string[] = ['Test', 'test'];
  form!: FormGroup;
  ngOnInit(): void {
    this.form = new FormGroup({
      'userData': new FormGroup({
        'email': new FormControl(null, [Validators.required, Validators.email]),
        'username': new FormControl(null, [Validators.required,
                                this.excludedUserNames.bind(this)]),
      }),

      'hobbies': new FormArray([])
    });
  }
  onSubmit() {
    console.log(this.form);
  }

  onAddingHobby() {
    const control = new FormControl(null, Validators.required);
    this.hobbies.push(control);
  }
  onRemovingHobby(i: number) {
    this.hobbies.removeAt(i);
  }

  excludedUserNames(fromControl: FormControl): { [s: string]: boolean } | null {
    if (this.excludedUserName.indexOf(fromControl.value) !== -1) {
      return { 'excludedUserName': true };
    }
    return null;
  }

  get hobbies() {
    return this.form.get('hobbies') as FormArray;
  }
}

Monday, October 11, 2021

Subject in Angular

 What is a Subject?

Subject is a special type of Observable in RxJs Library in which we can send our data to other components or services. A Subject is like an Observable but can multicast to many observers which means subject is at the same time an Observable and an Observer.

To understand the subject we take the below example. In this example, we display user name and its status on home component and on click of user name we display user info in user component.


Step 1 : Create user.model.ts

export class User{
    userName : string = '';
    userStatus : string = '';
    constructor(userName : string , userStatus : string){
        this.userName = userName;
        this.userStatus = userStatus;
    }
}

Step 2 : Create user.service.ts

selectedUserEmitter : Subject that send User type into another component

userList : default user list

import { EventEmitter, Injectable } from "@angular/core";
import { Subject } from "rxjs";
import { User } from "../model/user.model";
@Injectable({
    providedIn: 'root'
})
export class UserService {
    selectedUserEmitter = new Subject<User>();
    userList: User[] = [{ userName: 'Mark', userStatus: 'Active' },
    { userName: 'Cris', userStatus: 'Inactive' }];

    getUserList(): User[] {
        return this.userList.slice();
    }
}

Step 3 : generate home and user component

ng g c home

ng g c user

Step 4 : home.component.html 

<div class="row" *ngFor="let user of this.userList">
    <div class="span6">
        <a (click)="onClick(user)"> <label for="UserName">User Name</label> {{user.userName}} </a>
    </div>
    <div class="span6">
        <a (click)="onClick(user)"> <label for="Status">Status</label> {{user.userStatus}} </a>
    </div>
    <hr />

</div>

home.component.ts

export class HomeComponent implements OnInit, OnDestroy {
  userList : User[] = [];
  constructor(private userService: UserService) { }
  ngOnDestroy(): void {
    this.userService.activatedEmitter.unsubscribe();
  }

  ngOnInit(): void {
  this.userList = this.userService.getUserList();
  }

  onClick(user : User){
    this.userService.selectedUserEmitter.next(user);
  }
}


Step 4 : user.component.html

<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
    <p>{{this.selectedUserName}}</p>
    <p>{{this.selectedUserStatus}}</p>
</div>

user.component.ts

export class UserComponent implements OnInit,OnDestroy {
  selectedUserName : string = '';
  selectedUserStatus : string = '';
  constructor(private userService: UserService) { }
  ngOnDestroy(): void {
    this.userService.selectedUserEmitter.unsubscribe();
  }

  ngOnInit(): void {
    this.userService.selectedUserEmitter.subscribe(item => {
      this.selectedUserName = item.userName;
      this.selectedUserStatus = item.userStatus;
    });
  }
}

Step 5 : app.component.html

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <div class="panel panel-default">
        <div class="panel panel-heading">
          Details
        </div>
        <div class="panel panel-body">
          <app-home></app-home>
        </div>
      </div>
    </div>
  </div>
  <div class="row">
    <app-user></app-user>
  </div>
</div>