Angular
TypeScript-based web application framework. Developed by Google, providing comprehensive feature set for large enterprises, strong type safety, and reactive programming with RxJS.
GitHub Overview
angular/angular
Deliver web apps with confidence 🚀
Topics
Star History
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
- Angular Official Site
- Angular Official Documentation
- Angular CLI
- Angular DevTools
- Angular GitHub Repository
- Angular Material
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));
}
}