Angular

TypeScript-based web application framework. Developed by Google, providing comprehensive feature set for large enterprises, strong type safety, and reactive programming with RxJS.

TypeScriptFrameworkFrontendUIComponentsSPAPWA

GitHub Overview

angular/angular

Deliver web apps with confidence 🚀

Stars98,317
Watchers2,999
Forks26,424
Created:September 18, 2014
Language:TypeScript
License:MIT License

Topics

angularjavascriptpwatypescriptwebweb-frameworkweb-performance

Star History

angular/angular Star History
Data as of: 7/19/2025, 08:06 AM

Framework

Angular

Overview

Angular is a full-stack framework for building modern web applications, developed and maintained by Google.

Details

Angular is an open-source web application framework based on TypeScript. Launched in 2016 as the successor to AngularJS (1.x), it adopts a component-based architecture. As a full-stack framework, it provides all necessary features for web application development including routing, forms, HTTP communication, testing, and internationalization out of the box. Specialized for building Single Page Applications (SPAs), Angular is well-suited for enterprise-level large-scale application development. It ensures code maintainability and scalability through its Dependency Injection (DI) system, automates development environments through Angular CLI, and supports modern web development needs including Server-Side Rendering (SSR) and PWA capabilities. Also used in Google's internal products, Angular boasts high quality and stability.

Advantages and Disadvantages

Advantages

  • Full-stack framework: Provides all necessary development features out of the box
  • TypeScript by default: Ensures quality in large-scale development through type safety
  • Enterprise support: Continuous development and maintenance by Google with LTS support
  • Powerful CLI: Automates project generation, building, testing, and deployment
  • Excellent tooling: Enhanced development experience with Angular DevTools and Language Service
  • Enterprise-oriented: Designed for large-scale team development
  • PWA support: Supports Progressive Web App development
  • SSR/SSG support: Server-side rendering and static site generation

Disadvantages

  • Learning curve: Large framework with many concepts to master
  • Complexity: Can be overly complex for small-scale projects
  • Bundle size: Minimum configuration is larger than other libraries
  • Frequent updates: Major updates occur twice yearly, requiring migration
  • Configuration complexity: Initial setup and advanced customization can be complex

Key Links

Code Examples

Hello World

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<h1>Hello, Angular!</h1>`,
  styles: [`
    h1 {
      color: #1976d2;
      font-family: Arial, sans-serif;
    }
  `]
})
export class AppComponent {
  title = 'Hello World';
}
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent);

Components and Property Binding

// welcome.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-welcome',
  template: `
    <div class="welcome-container">
      <h2>Hello, {{ name }}!</h2>
      <p>You are {{ age }} years old and work in the {{ department }} department.</p>
      <p *ngIf="isAdmin">You have administrator privileges.</p>
    </div>
  `,
  styles: [`
    .welcome-container {
      padding: 20px;
      border: 1px solid #ccc;
      border-radius: 8px;
      margin: 10px 0;
    }
  `]
})
export class WelcomeComponent {
  @Input() name: string = '';
  @Input() age: number = 0;
  @Input() department: string = '';
  @Input() isAdmin: boolean = false;
}
// app.component.ts
import { Component } from '@angular/core';
import { WelcomeComponent } from './welcome.component';

@Component({
  selector: 'app-root',
  imports: [WelcomeComponent],
  template: `
    <app-welcome 
      name="John" 
      [age]="28" 
      department="Development" 
      [isAdmin]="true">
    </app-welcome>
    <app-welcome 
      name="Sarah" 
      [age]="32" 
      department="Sales" 
      [isAdmin]="false">
    </app-welcome>
  `
})
export class AppComponent {}

Services and Dependency Injection

// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://api.example.com/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl);
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`);
  }

  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }
}
// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserService, User } from './user.service';

@Component({
  selector: 'app-user-list',
  imports: [CommonModule],
  template: `
    <div class="user-list">
      <h2>User List</h2>
      <div *ngIf="loading">Loading...</div>
      <div *ngIf="error" class="error">Error: {{ error }}</div>
      <ul *ngIf="!loading && !error">
        <li *ngFor="let user of users" class="user-item">
          <strong>{{ user.name }}</strong> - {{ user.email }}
        </li>
      </ul>
    </div>
  `,
  styles: [`
    .user-list { padding: 20px; }
    .error { color: red; }
    .user-item { margin: 10px 0; padding: 10px; border: 1px solid #ddd; }
  `]
})
export class UserListComponent implements OnInit {
  users: User[] = [];
  loading = false;
  error: string | null = null;

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.loadUsers();
  }

  private loadUsers() {
    this.loading = true;
    this.userService.getUsers().subscribe({
      next: (users) => {
        this.users = users;
        this.loading = false;
      },
      error: (err) => {
        this.error = err.message;
        this.loading = false;
      }
    });
  }
}

Routing

// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ProductComponent } from './product/product.component';

export const routes: Routes = [
  { path: '', component: HomeComponent, title: 'Home' },
  { path: 'about', component: AboutComponent, title: 'About' },
  { path: 'product/:id', component: ProductComponent, title: 'Product Details' },
  { path: '**', redirectTo: '' }
];
// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, RouterLink],
  template: `
    <nav>
      <a routerLink="/">Home</a>
      <a routerLink="/about">About</a>
      <a routerLink="/product/1">Product 1</a>
    </nav>
    <router-outlet />
  `,
  styles: [`
    nav { padding: 20px; background: #f0f0f0; }
    nav a { margin-right: 20px; text-decoration: none; color: #007bff; }
  `]
})
export class AppComponent {}

Reactive Forms

// contact-form.component.ts
import { Component, OnInit } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-contact-form',
  imports: [ReactiveFormsModule, CommonModule],
  template: `
    <div class="form-container">
      <h2>Contact Form</h2>
      
      <form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
        <div class="form-group">
          <label for="name">Name *</label>
          <input 
            id="name"
            type="text" 
            formControlName="name"
            [class.error]="isFieldInvalid('name')">
          <div *ngIf="isFieldInvalid('name')" class="error-message">
            <span *ngIf="contactForm.get('name')?.errors?.['required']">
              Name is required
            </span>
            <span *ngIf="contactForm.get('name')?.errors?.['minlength']">
              Name must be at least 2 characters
            </span>
          </div>
        </div>

        <div class="form-group">
          <label for="email">Email *</label>
          <input 
            id="email"
            type="email" 
            formControlName="email"
            [class.error]="isFieldInvalid('email')">
          <div *ngIf="isFieldInvalid('email')" class="error-message">
            <span *ngIf="contactForm.get('email')?.errors?.['required']">
              Email is required
            </span>
            <span *ngIf="contactForm.get('email')?.errors?.['email']">
              Please enter a valid email address
            </span>
          </div>
        </div>

        <div class="form-group">
          <label for="message">Message *</label>
          <textarea 
            id="message"
            formControlName="message"
            rows="5"
            [class.error]="isFieldInvalid('message')">
          </textarea>
          <div *ngIf="isFieldInvalid('message')" class="error-message">
            <span *ngIf="contactForm.get('message')?.errors?.['required']">
              Message is required
            </span>
            <span *ngIf="contactForm.get('message')?.errors?.['minlength']">
              Message must be at least 10 characters
            </span>
          </div>
        </div>

        <button 
          type="submit" 
          [disabled]="contactForm.invalid || isSubmitting"
          class="submit-btn">
          {{ isSubmitting ? 'Submitting...' : 'Submit' }}
        </button>
      </form>

      <div *ngIf="isSubmitted" class="success-message">
        Thank you for your message. We'll get back to you soon!
      </div>
    </div>
  `,
  styles: [`
    .form-container { max-width: 600px; margin: 0 auto; padding: 20px; }
    .form-group { margin-bottom: 20px; }
    label { display: block; margin-bottom: 5px; font-weight: bold; }
    input, textarea { 
      width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; 
    }
    input.error, textarea.error { border-color: #dc3545; }
    .error-message { color: #dc3545; font-size: 0.875em; margin-top: 5px; }
    .submit-btn { 
      background: #007bff; color: white; padding: 12px 24px; 
      border: none; border-radius: 4px; cursor: pointer; 
    }
    .submit-btn:disabled { background: #6c757d; cursor: not-allowed; }
    .success-message { 
      background: #d4edda; color: #155724; padding: 12px; 
      border-radius: 4px; margin-top: 20px; 
    }
  `]
})
export class ContactFormComponent implements OnInit {
  contactForm!: FormGroup;
  isSubmitting = false;
  isSubmitted = false;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.contactForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      email: ['', [Validators.required, Validators.email]],
      message: ['', [Validators.required, Validators.minLength(10)]]
    });
  }

  isFieldInvalid(fieldName: string): boolean {
    const field = this.contactForm.get(fieldName);
    return !!(field && field.invalid && (field.dirty || field.touched));
  }

  async onSubmit() {
    if (this.contactForm.valid) {
      this.isSubmitting = true;
      
      try {
        // Simulate API call
        await this.simulateApiCall(this.contactForm.value);
        
        this.isSubmitted = true;
        this.contactForm.reset();
      } catch (error) {
        console.error('Submission error:', error);
        alert('Failed to submit. Please try again.');
      } finally {
        this.isSubmitting = false;
      }
    } else {
      // Mark all fields as touched to show errors
      Object.keys(this.contactForm.controls).forEach(key => {
        this.contactForm.get(key)?.markAsTouched();
      });
    }
  }

  private simulateApiCall(data: any): Promise<void> {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log('Submitted data:', data);
        resolve();
      }, 2000);
    });
  }
}

HTTP Client

// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';

export interface ApiResponse<T> {
  data: T;
  message: string;
  status: number;
}

export interface Post {
  id: number;
  title: string;
  content: string;
  authorId: number;
  createdAt: string;
}

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  private readonly baseUrl = 'https://api.example.com';

  constructor(private http: HttpClient) {}

  // GET request
  getPosts(): Observable<ApiResponse<Post[]>> {
    return this.http.get<ApiResponse<Post[]>>(`${this.baseUrl}/posts`)
      .pipe(
        retry(3),
        catchError(this.handleError)
      );
  }

  // GET with parameters
  getPostsByAuthor(authorId: number, page: number = 1): Observable<ApiResponse<Post[]>> {
    const params = new HttpParams()
      .set('authorId', authorId.toString())
      .set('page', page.toString())
      .set('limit', '10');

    return this.http.get<ApiResponse<Post[]>>(`${this.baseUrl}/posts`, { params })
      .pipe(
        catchError(this.handleError)
      );
  }

  // POST request
  createPost(post: Omit<Post, 'id' | 'createdAt'>): Observable<ApiResponse<Post>> {
    const headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${this.getAuthToken()}`
    });

    return this.http.post<ApiResponse<Post>>(`${this.baseUrl}/posts`, post, { headers })
      .pipe(
        catchError(this.handleError)
      );
  }

  private getAuthToken(): string {
    return localStorage.getItem('authToken') || '';
  }

  private handleError(error: any): Observable<never> {
    let errorMessage = 'A server error occurred';
    
    if (error.error instanceof ErrorEvent) {
      // Client-side error
      errorMessage = `Error: ${error.error.message}`;
    } else {
      // Server-side error
      switch (error.status) {
        case 400:
          errorMessage = 'Bad request';
          break;
        case 401:
          errorMessage = 'Authentication required';
          break;
        case 403:
          errorMessage = 'Access forbidden';
          break;
        case 404:
          errorMessage = 'Resource not found';
          break;
        case 500:
          errorMessage = 'Internal server error';
          break;
        default:
          errorMessage = `Error code: ${error.status}`;
      }
    }

    console.error('HTTP Error:', error);
    return throwError(() => new Error(errorMessage));
  }
}