Angular is the enterprise SPA framework of choice for teams that value TypeScript-first development, strong conventions, and a comprehensive built-in toolchain. Whether you're building a standalone astrology app or adding horoscope features to an existing enterprise portal, Angular's dependency injection, reactive forms, and RxJS integration make it exceptionally well-suited for working with REST APIs like Vedika.
This tutorial walks you through building a complete astrology dashboard — birth chart input, AI-powered predictions, zodiac horoscope display, and a live chatbot — using Angular 17 standalone components and Vedika API, the only astrology API with a built-in AI engine.
What You'll Build: A production-ready Angular 17 astrology dashboard with a birth chart reactive form, AI chatbot component, daily horoscope panel, and Angular Material UI. Complete with HttpClient service, RxJS error handling, lazy-loaded routing, and environment-based API key management.
Why Angular for Astrology Applications?
TypeScript-First
Full type safety for Vedika API response models — catch shape mismatches at compile time, not in production.
RxJS Built-In
Observables are a natural fit for streaming API responses, combining multiple astrology data streams, and reactive UI updates.
Angular Material
Polished datepicker, autocomplete, progress indicators, and card components — production UI without custom CSS.
Enterprise Scale
Dependency injection, lazy loading, and a structured module system make Angular dashboards maintainable as they grow.
Prerequisites
- Node.js 20 LTS and npm
- Angular CLI 17+ (
npm install -g @angular/cli)
- Vedika API key — get yours here
- Familiarity with TypeScript and Angular basics
Step 1: Create the Angular Project
Generate a new standalone Angular 17 project with routing and SCSS styling:
ng new astrology-dashboard \
--routing=true \
--style=scss \
--standalone=true
cd astrology-dashboard
ng add @angular/material
npm install
Environment Configuration
Store the API key in Angular's environment files — never in component code. For production, inject the key server-side and proxy through your own backend:
export const environment = {
production: false,
vedikaApiUrl: 'http://localhost:4200/api-proxy',
vedikaApiKey: 'YOUR_DEV_API_KEY'
};
export const environment = {
production: true,
vedikaApiUrl: '/api-proxy',
vedikaApiKey: ''
};
Step 2: TypeScript Model Interfaces
Define strongly-typed interfaces for all Vedika API request and response shapes. This gives you autocomplete and compile-time safety throughout your components:
export interface BirthDetails {
datetime: string;
latitude: number;
longitude: number;
timezone: string;
}
export interface AstrologyQuery {
question: string;
birthDetails?: BirthDetails;
system?: 'vedic' | 'western' | 'kp';
}
export interface AstrologyResponse {
answer: string;
system: string;
birthChart?: {
planets: PlanetData[];
ascendant: string;
currentDasha: string;
};
usage?: {
tokensUsed: number;
cost: number;
};
}
export interface PlanetData {
name: string;
sign: string;
house: number;
degree: number;
dignity: string;
isRetrograde: boolean;
}
export interface ChatMessage {
role: 'user' | 'assistant';
content: string;
timestamp: Date;
}
Step 3: Vedika API Service
Create an injectable service that encapsulates all API calls. The service uses HttpClient and returns typed Observables that components can subscribe to:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError, shareReplay } from 'rxjs';
import { catchError, map, retry } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { AstrologyQuery, AstrologyResponse, BirthDetails } from '../models/astrology.models';
@Injectable({ providedIn: 'root' })
export class VedikaService {
private readonly baseUrl = environment.vedikaApiUrl;
private readonly headers = new HttpHeaders({
'Content-Type': 'application/json',
'x-api-key': environment.vedikaApiKey
});
constructor(private http: HttpClient) {}
query(query: AstrologyQuery): Observable<AstrologyResponse> {
return this.http.post<AstrologyResponse>(
`${this.baseUrl}/api/vedika/chat`,
query,
{ headers: this.headers }
).pipe(
retry(1),
catchError(this.handleError)
);
}
getBirthChart(details: BirthDetails): Observable<AstrologyResponse> {
return this.query({
question: 'Generate my complete Vedic birth chart with all planetary positions, active yogas, and current dasha period.',
birthDetails: details
}).pipe(
shareReplay(1)
);
}
getDailyHoroscope(
zodiacSign: string,
details?: BirthDetails
): Observable<AstrologyResponse> {
return this.query({
question: `What is today's detailed horoscope for ${zodiacSign}? ` +
'Cover career, relationships, health, and wealth. Be specific and practical.',
birthDetails: details
});
}
getCompatibility(
person1: BirthDetails,
zodiacPair: string
): Observable<AstrologyResponse> {
return this.query({
question: `Calculate Ashtakoot Guna Milan compatibility for ${zodiacPair}. ` +
'Provide the score out of 36 and detailed assessment.',
birthDetails: person1
});
}
private handleError(error: HttpErrorResponse): Observable<never> {
let message = 'An error occurred';
if (error.status === 402) message = 'Insufficient API credits. Please top up your wallet.';
else if (error.status === 429) message = 'Rate limit reached. Please try again in a moment.';
else if (error.status === 401) message = 'Invalid API key. Check your configuration.';
else if (error.error?.message) message = error.error.message;
return throwError(() => new Error(message));
}
}
Step 4: Birth Chart Form Component
Build a reactive form that collects birth details with Angular Material components and client-side validation before calling the API:
import { Component, OnInit, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { VedikaService } from '../../services/vedika.service';
import { AstrologyResponse } from '../../models/astrology.models';
@Component({
selector: 'app-birth-chart-form',
standalone: true,
imports: [
CommonModule, ReactiveFormsModule,
MatFormFieldModule, MatInputModule,
MatDatepickerModule, MatButtonModule,
MatProgressSpinnerModule
],
template: `
<mat-card class="birth-chart-card">
<mat-card-header>
<mat-card-title>Enter Birth Details</mat-card-title>
<mat-card-subtitle>Powered by Swiss Ephemeris astronomical precision</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form [formGroup]="birthForm" (ngSubmit)="onSubmit()">
<mat-form-field appearance="outline" class="full-width">
<mat-label>Date of Birth</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date"
placeholder="DD/MM/YYYY">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="birthForm.get('date')?.hasError('required')">
Birth date is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="full-width">
<mat-label>Time of Birth (HH:MM)</mat-label>
<input matInput formControlName="time" placeholder="14:30"
pattern="^([01]?[0-9]|2[0-3]):[0-5][0-9]$">
<mat-error *ngIf="birthForm.get('time')?.hasError('pattern')">
Enter time in HH:MM format (e.g. 14:30)
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Latitude</mat-label>
<input matInput type="number" formControlName="latitude"
placeholder="28.6139" step="0.0001">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Longitude</mat-label>
<input matInput type="number" formControlName="longitude"
placeholder="77.2090" step="0.0001">
</mat-form-field>
<button mat-raised-button color="warn" type="submit"
[disabled]="birthForm.invalid || loading()">
<mat-spinner diameter="20" *ngIf="loading()"></mat-spinner>
<span *ngIf="!loading()">Generate Birth Chart</span>
</button>
</form>
</mat-card-content>
</mat-card>
<mat-card *ngIf="result()" class="result-card">
<mat-card-header>
<mat-card-title>Your Vedic Birth Chart</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>{{ result()?.answer }}</p>
</mat-card-content>
</mat-card>
<mat-error *ngIf="error()">{{ error() }}</mat-error>
`
})
export class BirthChartFormComponent implements OnInit {
birthForm!: FormGroup;
loading = signal(false);
result = signal<AstrologyResponse | null>(null);
error = signal<string | null>(null);
constructor(
private fb: FormBuilder,
private vedikaService: VedikaService
) {}
ngOnInit() {
this.birthForm = this.fb.group({
date: [null, Validators.required],
time: ['12:00', [Validators.required, Validators.pattern(/^([01]?\d|2[0-3]):[0-5]\d$/)]],
latitude: [28.6139, [Validators.required, Validators.min(-90), Validators.max(90)]],
longitude: [77.2090, [Validators.required, Validators.min(-180), Validators.max(180)]]
});
}
onSubmit() {
if (this.birthForm.invalid) return;
const { date, time, latitude, longitude } = this.birthForm.value;
const dateStr = date.toISOString().split('T')[0];
this.loading.set(true);
this.error.set(null);
this.vedikaService.getBirthChart({
datetime: `${dateStr}T${time}:00`,
latitude, longitude,
timezone: '+05:30'
}).subscribe({
next: (res) => { this.result.set(res); this.loading.set(false); },
error: (err) => { this.error.set(err.message); this.loading.set(false); }
});
}
}
Ready to Build Your Angular Astrology Dashboard?
Try the FREE Sandbox — 65 mock endpoints, no credit card required. Plans from $12/month.
Explore the Sandbox
Step 5: Daily Horoscope Component
Create a zodiac picker component that loads horoscopes on selection using switchMap to cancel pending requests when the user changes their sign:
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { VedikaService } from '../../services/vedika.service';
const ZODIAC_SIGNS = [
{ name: 'Aries', symbol: '♈' },
{ name: 'Taurus', symbol: '♉' },
{ name: 'Gemini', symbol: '♊' },
{ name: 'Cancer', symbol: '♋' },
{ name: 'Leo', symbol: '♌' },
{ name: 'Virgo', symbol: '♍' },
{ name: 'Libra', symbol: '♎' },
{ name: 'Scorpio', symbol: '♏' },
{ name: 'Sagittarius', symbol: '♐' },
{ name: 'Capricorn', symbol: '♑' },
{ name: 'Aquarius', symbol: '♒' },
{ name: 'Pisces', symbol: '♓' }
];
@Component({
selector: 'app-daily-horoscope',
standalone: true,
imports: [CommonModule],
template: `
<div class="zodiac-grid">
<button *ngFor="let sign of zodiacSigns"
class="zodiac-btn"
[class.active]="selectedSign() === sign.name"
(click)="selectSign(sign.name)">
<span class="zodiac-symbol">{{ sign.symbol }}</span>
<span class="zodiac-name">{{ sign.name }}</span>
</button>
</div>
<div class="horoscope-result" *ngIf="selectedSign()">
<div class="loading-overlay" *ngIf="loading()">
<div class="spinner"></div>
<p>Consulting the stars...</p>
</div>
<div class="prediction" *ngIf="prediction() && !loading()">
<h3>Today's Horoscope — {{ selectedSign() }}</h3>
<p>{{ prediction() }}</p>
</div>
</div>
`
})
export class DailyHoroscopeComponent {
zodiacSigns = ZODIAC_SIGNS;
selectedSign = signal('');
prediction = signal('');
loading = signal(false);
private signChange$ = new Subject<string>();
constructor(private vedikaService: VedikaService) {
this.signChange$.pipe(
tap(() => { this.loading.set(true); this.prediction.set(''); }),
switchMap(sign => this.vedikaService.getDailyHoroscope(sign))
).subscribe({
next: (res) => {
this.prediction.set(res.answer);
this.loading.set(false);
},
error: () => this.loading.set(false)
});
}
selectSign(sign: string) {
this.selectedSign.set(sign);
this.signChange$.next(sign);
}
}
Step 6: AI Chat Component
Build a conversational AI chatbot component that maintains message history and streams responses from Vedika AI:
@Component({
selector: 'app-astro-chat',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, MatInputModule,
MatButtonModule, MatProgressSpinnerModule],
template: `
<div class="chat-container" #chatContainer>
<div class="messages">
<div *ngFor="let msg of messages()"
[class]="'message ' + msg.role">
<div class="bubble">{{ msg.content }}</div>
<span class="timestamp">{{ msg.timestamp | date:'HH:mm' }}</span>
</div>
<div class="typing" *ngIf="loading()">
<span></span><span></span><span></span>
</div>
</div>
</div>
<div class="input-row">
<mat-form-field appearance="outline" class="flex-grow">
<input matInput [formControl]="inputControl"
placeholder="Ask about career, love, health, timing..."
(keyup.enter)="sendMessage()">
</mat-form-field>
<button mat-raised-button color="warn"
(click)="sendMessage()"
[disabled]="loading() || !inputControl.value?.trim()">
Ask Vedika AI
</button>
</div>
`
})
export class AstroChatComponent {
messages = signal<ChatMessage[]>([]);
loading = signal(false);
inputControl = new FormControl('');
@Input() birthDetails!: BirthDetails;
constructor(private vedikaService: VedikaService) {}
sendMessage() {
const question = this.inputControl.value?.trim();
if (!question || this.loading()) return;
this.messages.update(msgs => [...msgs, {
role: 'user', content: question, timestamp: new Date()
}]);
this.inputControl.reset();
this.loading.set(true);
this.vedikaService.query({ question, birthDetails: this.birthDetails })
.subscribe({
next: (res) => {
this.messages.update(msgs => [...msgs, {
role: 'assistant', content: res.answer, timestamp: new Date()
}]);
this.loading.set(false);
},
error: (err) => {
this.messages.update(msgs => [...msgs, {
role: 'assistant', content: `Error: ${err.message}`, timestamp: new Date()
}]);
this.loading.set(false);
}
});
}
}
Step 7: Application Routing
Configure lazy-loaded routes so Angular only loads what's needed — critical for dashboard performance:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
loadComponent: () => import('./pages/dashboard/dashboard.component')
.then(m => m.DashboardComponent)
},
{
path: 'birth-chart',
loadComponent: () => import('./components/birth-chart-form/birth-chart-form.component')
.then(m => m.BirthChartFormComponent)
},
{
path: 'horoscope',
loadComponent: () => import('./components/daily-horoscope/daily-horoscope.component')
.then(m => m.DailyHoroscopeComponent)
},
{
path: 'chat',
loadComponent: () => import('./components/astro-chat/astro-chat.component')
.then(m => m.AstroChatComponent)
}
];
Step 8: Proxy Configuration for Development
Set up Angular's dev server proxy to forward API calls to Vedika — avoids CORS issues during local development:
{
"/api-proxy": {
"target": "https://api.vedika.io",
"secure": true,
"changeOrigin": true,
"pathRewrite": { "^/api-proxy": "" },
"logLevel": "debug"
}
}
{
"serve": {
"options": {
"proxyConfig": "proxy.conf.json"
}
}
}
ng serve
ng build --configuration production
ng test
ng generate component components/panchang-calendar --standalone
Why Choose Vedika API Over Competitors?
AI Chatbot Included
One endpoint answers any natural language astrology question. Your Angular chatbot component becomes fully functional with a single API call — no complex orchestration.
140+ Calculations
Birth charts, ashtakavarga, compatibility, muhurta, numerology, panchang — comprehensive coverage that competitors simply don't match.
TypeScript-Friendly
Clean JSON responses that map directly to TypeScript interfaces. No XML parsing, no unusual data formats — just pure JSON.
MCP Server
The world's first astrology MCP server. Integrate Vedika directly with AI agent frameworks for the next generation of astrology applications.
30 Languages
Hindi, Tamil, Telugu, Bengali, Gujarati, Marathi, and 24 more. Build truly multilingual Angular dashboards with a single API.
$12/Month Starting
Transparent wallet-based billing. Start at $12/month Starter — far cheaper than competitors charging $19-49 for fewer endpoints.
Pricing
Vedika API pricing scales with your needs:
- Starter: $12/month — perfect for Angular side projects and MVPs
- Professional: $60/month — for growing SaaS dashboards
- Business: $120/month — for production enterprise portals
- Enterprise: $240/month — dedicated support, top-up credits, SLA
All plans include the full 140+ endpoint catalog, AI chatbot, and 30-language support. View full pricing details.
Conclusion
Angular's strong TypeScript integration, RxJS-first design, and Angular Material component library make it an excellent choice for building production astrology dashboards. The key patterns from this tutorial:
- Injectable service with typed Observables — separates HTTP logic from components cleanly
- Reactive forms with Angular Material — validation, datepicker, and progress indicators built-in
- Angular Signals for component state — cleaner than BehaviorSubjects for simple loading/result tracking
- switchMap for user selections — automatically cancels in-flight requests when the user changes zodiac sign
- Lazy-loaded routes — only load dashboard modules when needed, keeping initial bundle small
- Proxy config — keeps API key server-side in all environments
Next steps:
- Get your API key at vedika.io/dashboard
- Explore the free sandbox — 65 mock endpoints, zero cost
- Browse Angular-specific docs for all 140+ endpoints
- Add an interceptor to attach the API key header globally instead of per-service
- Implement an NgRx store for cross-component birth chart state sharing
About Vedika Intelligence: Vedika is the only B2B astrology API with an AI-powered chatbot engine, serving production applications worldwide. The Vedika Intelligence Engine enables natural language astrology queries with Swiss Ephemeris-verified precision, supporting both Vedic and Western astrology across 30 languages.