Full Trust European Hosting

BLOG about Full Trust Hosting and Its Technology - Dedicated to European Windows Hosting Customer

AngularJS Hosting Europe - HostForLIFE.eu :: Learn HTTP Interceptors in Angular

clock March 27, 2025 07:39 by author Peter

The HttpClient module in Angular has a feature called an HTTP Interceptor that enables developers to examine and modify HTTP requests and answers globally before application logic handles them.  Tasks including adding authorization tokens to headers, logging, resolving failures, and altering requests and answers are all frequently performed by interceptors.

Key Features of HTTP Interceptors

  • Middleware for HTTP Requests and Responses: Interceptors sit between the application and the backend, processing all HTTP requests and responses.
  • Global Scope: Once an interceptor is configured, it applies to all HTTP requests and responses in the application.
  • Chaining: Multiple interceptors can be implemented, and they are executed in the order they are provided.

Benefits of HTTP Interceptors
Following are some of the key benefits of using HTTP Interceptors in Angular.

  • Testability and reusability: Interceptors are easy to test in isolation, allowing you to ensure that each interceptor behaves correctly
  • Centralized code for cross-cutting concerns: HTTP Interceptors allow you to define logic for common tasks, such as authentication, logging, error handling, or adding headers, in a centralized location.
  • Global application-level modifications: Interceptors operate globally, intercepting all HTTP requests and responses made by the Angular application. This means you can apply changes or perform actions consistently across multiple API calls without having to modify each individual request or response manually.
  • Error handling and logging: Interceptors can be utilized to handle errors globally, providing a consistent approach to error reporting and handling throughout the application.
  • Caching and request/response manipulation: HTTP Interceptors can be leveraged to implement caching mechanisms, reducing redundant requests and optimizing the application’s performance.
  • Separation of concerns: By using HTTP Interceptors, you can keep concerns related to data fetching and communication (HTTP) separate from the business logic of your components and services.
  • Security and authentication: Interceptors are commonly used to add authorization headers or authentication tokens to outgoing requests. This ensures that the user’s authentication status is automatically included in API calls without the need to explicitly set headers in every request.
  • Easy integration with third-party libraries: Interceptors can be used to integrate with third-party libraries or APIs seamlessly. For example, you can apply a specific format to API responses that are expected by a charting library or a data visualization tool.

Logging Interceptor

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpResponse,
} from '@angular/common/http';
import { Observable, tap } from 'rxjs';

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    console.log('Outgoing HTTP request', request);
    return next.handle(request).pipe(
      tap((event: HttpEvent<any>) => {
        console.log('Incoming HTTP response', event);
      })
    );
  }
}


Provide an interceptor in the app module.
import { LoggingInterceptor } from './interceptors/logging.interceptor';

providers: [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: LoggingInterceptor,
    multi: true,
  },
];

Adding Headers to Requests

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class HeadersInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    console.log(request);
    const GUID = 'f4189b26-01af-432c-bcd8-cb4bc7e90980';
    const modifiedRequest = request.clone({
      setHeaders: {
        GUID,
      },
    });
    return next.handle(modifiedRequest);
  }
}


Provide an interceptor in the app module.
import { HeadersInterceptor  } from './interceptors/headers.interceptor'

providers: [
    {
      provide: HTTP_INTERCEPTORS, useClass: HeadersInterceptor, multi: true
    }
  ]

Error Handling Interceptor

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse,
} from '@angular/common/http';
import { Observable, catchError, throwError } from 'rxjs';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        // Handle the error here
        console.error('error occurred:', error);
        // throw error as per requirement
        return throwError(error);
      })
    );
  }
}

Provide an interceptor in the app module.
import { ErrorInterceptor } from './interceptors/error.interceptor';

  providers: [
    {
      provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true
    }
  ]

Authentication Interceptor
CLI command: ng generate interceptor auth.
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service'; // Service to get the token

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Get the token from the AuthService
    const authToken = this.authService.getToken();

    // Clone the request and add the Authorization header
    const authReq = authToken
      ? req.clone({
          headers: req.headers.set('Authorization', `Bearer ${authToken}`),
        })
      : req;

    return next.handle(authReq);
  }
}


Create the AuthService
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private token: string | null = null;

  // Simulate storing and retrieving the token
  setToken(token: string): void {
    this.token = token;
  }

  getToken(): string | null {
    return this.token;
  }

  clearToken(): void {
    this.token = null;
  }
}


Register the Interceptor

import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true, // Allow multiple interceptors
    },
  ],
})
export class AppModule {}


Handle Token Expiration

import { Router } from '@angular/router';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService, private router: Router) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authToken = this.authService.getToken();

    const authReq = authToken
      ? req.clone({
          headers: req.headers.set('Authorization', `Bearer ${authToken}`),
        })
      : req;

    return next.handle(authReq).pipe(
      catchError((error) => {
        if (error.status === 401) {
          // Redirect to login page on unauthorized response
          this.authService.clearToken();
          this.router.navigate(['/login']);
        }
        return throwError(error);
      })
    );
  }
}



AngularJS Hosting Europe - HostForLIFE.eu :: Using Angular to Create an Eye Catching Dashboard

clock March 17, 2025 07:29 by author Peter

With this post, we'll use Recharts with an Angular frontend and a Node.js backend to build a dashboard with stunning charts.

To concentrate on the UI for the time being, we will use hardcoded data. Real data can be connected later.

1.1 Install Angular CLI
Make sure you have Node.js installed. Then install Angular CLI globally:
npm install -g @angular/cli

1.2 Create a New Angular Project
ng new dashboard-app
cd dashboard-app

1.3  Create the Dashboard Component
We’ll create a DashboardComponent with hardcoded data for revenue, user activity, and sales. The component uses Chart.js to render three charts: a line chart for revenue, a line chart for user activity, and a bar chart for sales by category.

Here’s the code for dashboard.component.ts:
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Chart } from 'chart.js/auto';

interface ChartData {
  month: string;
  revenue: number;
}

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="dashboard-container p-8">
      <h1 class="text-4xl font-bold mb-8">Dashboard</h1>

      <!-- Stats Grid -->
      <div class="stats-grid mb-8">
        <div class="stat-card" *ngFor="let stat of stats">
          <div class="stat-header">
            <span class="stat-title">{{stat.title}}</span>
            <i class="stat-icon" [class]="stat.icon"></i>
          </div>
          <div class="stat-value">{{stat.value}}</div>
        </div>
      </div>

      <!-- Charts -->
      <div class="charts-grid mb-8">
        <div class="chart-container">
          <canvas id="revenueChart"></canvas>
        </div>
        <div class="chart-container">
          <canvas id="userActivityChart"></canvas>
        </div>
      </div>

      <div class="chart-container">
        <canvas id="salesChart"></canvas>
      </div>
    </div>
  `,
  styles: [`
    .dashboard-container {
      max-width: 1400px;
      margin: 0 auto;
    }

    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 1rem;
    }

    .stat-card {
      background: white;
      padding: 1.5rem;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }

    .stat-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 0.5rem;
    }

    .stat-title {
      font-size: 0.875rem;
      color: #64748b;
    }

    .stat-value {
      font-size: 1.5rem;
      font-weight: bold;
      color: #1e293b;
    }

    .charts-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
      gap: 1rem;
    }

    .chart-container {
      background: white;
      padding: 1rem;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
  `]
})
export class DashboardComponent implements OnInit {
  stats = [
    { title: 'Total Revenue', value: '$443,000', icon: 'bi bi-currency-dollar' },
    { title: 'Average Order', value: '$245', icon: 'bi bi-credit-card' },
    { title: 'Total Customers', value: '12.5K', icon: 'bi bi-people' },
    { title: 'Total Orders', value: '8.2K', icon: 'bi bi-cart' }
  ];

  revenueData = {
    labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    datasets: [{
      label: 'Revenue',
      data: [24000, 26000, 32000, 28000, 35000, 42000, 39000, 45000, 48000, 52000, 49000, 55000],
      borderColor: 'rgb(75, 192, 192)',
      tension: 0.1
    }]
  };

  userActivityData = {
    labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    datasets: [
      {
        label: 'Active Users',
        data: [3200, 3800, 4200, 3900, 3600, 2900, 2800],
        borderColor: 'rgb(75, 192, 192)',
        tension: 0.1
      },
      {
        label: 'New Users',
        data: [1400, 1600, 1800, 1500, 1300, 1000, 900],
        borderColor: 'rgb(255, 99, 132)',
        tension: 0.1
      }
    ]
  };

  salesData = {
    labels: ['Electronics', 'Clothing', 'Books', 'Home', 'Sports'],
    datasets: [{
      label: 'Sales by Category',
      data: [42000, 28000, 15000, 22000, 18000],
      backgroundColor: [
        'rgba(255, 99, 132, 0.5)',
        'rgba(54, 162, 235, 0.5)',
        'rgba(255, 206, 86, 0.5)',
        'rgba(75, 192, 192, 0.5)',
        'rgba(153, 102, 255, 0.5)'
      ]
    }]
  };

  constructor() {}

  ngOnInit(): void {
    this.createCharts();
  }

  private createCharts(): void {
    // Revenue Chart
    new Chart('revenueChart', {
      type: 'line',
      data: this.revenueData,
      options: {
        responsive: true,
        maintainAspectRatio: false
      }
    });

    // User Activity Chart
    new Chart('userActivityChart', {
      type: 'line',
      data: this.userActivityData,
      options: {
        responsive: true,
        maintainAspectRatio: false
      }
    });

    // Sales Chart
    new Chart('salesChart', {
      type: 'bar',
      data: this.salesData,
      options: {
        responsive: true,
        maintainAspectRatio: false
      }
    });
  }
}


Code in app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule],
  template: `
    <div class="app-container">
      <router-outlet></router-outlet>
    </div>
  `,
  styles: [`
    .app-container {
      min-height: 100vh;
      background-color: #f8f9fa;
      padding: 1rem;
    }
  `]
})
export class AppComponent {
  title = 'dashboard';
}


Code for not-found.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-not-found',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="not-found-container">
      <div class="error-card">
        <div class="error-header">
          <i class="bi bi-exclamation-circle text-red-500"></i>
          <h1>404 Page Not Found</h1>
        </div>
        <p>The page you're looking for doesn't exist.</p>
      </div>
    </div>
  `,
  styles: [`
    .not-found-container {
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #f8f9fa;
      padding: 1rem;
    }

    .error-card {
      background: white;
      padding: 2rem;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      max-width: 400px;
      width: 100%;
    }

    .error-header {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      margin-bottom: 1rem;
    }

    h1 {
      font-size: 1.5rem;
      font-weight: bold;
      color: #1e293b;
    }

    p {
      color: #64748b;
    }
  `]
})
export class NotFoundComponent {}

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () => import('./dashboard/dashboard.component')
      .then(m => m.DashboardComponent)
  },
  {
    path: '**',
    loadComponent: () => import('./not-found/not-found.component')
      .then(m => m.NotFoundComponent)
  }
];

Server Side code - index.js
import express, { type Request, Response, NextFunction } from "express";
import { registerRoutes } from "./routes";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";

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

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// Logging middleware
app.use((req, res, next) => {
  const start = Date.now();
  const path = req.path;
  let capturedJsonResponse: Record<string, any> | undefined = undefined;

  const originalResJson = res.json;
  res.json = function (bodyJson, ...args) {
    capturedJsonResponse = bodyJson;
    return originalResJson.apply(res, [bodyJson, ...args]);
  };

  res.on("finish", () => {
    const duration = Date.now() - start;
    if (path.startsWith("/api")) {
      let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`;
      if (capturedJsonResponse) {
        logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
      }

      if (logLine.length > 80) {
        logLine = logLine.slice(0, 79) + "…";
      }

      console.log(`${new Date().toLocaleTimeString()} [express] ${logLine}`);
    }
  });

  next();
});

(async () => {
  const server = await registerRoutes(app);

  app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
    const status = err.status || err.statusCode || 500;
    const message = err.message || "Internal Server Error";
    res.status(status).json({ message });
    throw err;
  });

  // Serve static files from the Angular build output directory
  const distPath = path.resolve(__dirname, "..", "dist", "public");
  app.use(express.static(distPath));

  // Always return index.html for any non-API routes (Angular routing)
  app.get("*", (_req, res) => {
    res.sendFile(path.join(distPath, "index.html"));
  });

  const port = 5000;
  server.listen({
    port,
    host: "0.0.0.0",
    reusePort: true,
  }, () => {
    console.log(`${new Date().toLocaleTimeString()} [express] serving on port ${port}`);
  });
})();

angular.json file
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "dashboard": {
      "projectType": "application",
      "schematics": {},
      "root": "client",
      "sourceRoot": "client/src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/public",
            "index": "client/src/index.html",
            "main": "client/src/main.ts",
            "polyfills": ["zone.js"],
            "tsConfig": "tsconfig.json",
            "assets": [
              "client/src/favicon.ico",
              "client/src/assets"
            ],
            "styles": [
              "client/src/styles.css",
              "node_modules/bootstrap-icons/font/bootstrap-icons.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "fileReplacements": [
                {
                  "replace": "client/src/environments/environment.ts",
                  "with": "client/src/environments/environment.prod.ts"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "browserTarget": "dashboard:build:production"
            },
            "development": {
              "browserTarget": "dashboard:build:development"
            }
          },
          "defaultConfiguration": "development"
        }
      }
    }
  }
}

This post showed how to use Angular and Node.js to create an eye-catching dashboard. We produced adaptable and interactive charts that make data come to life by utilizing Chart.js. This stack is perfect for creating scalable apps because of Angular's modular design and Node.js's speed. Additional charts, authentication, and the integration of real-time data sources are possible future enhancements. You may design dashboards of expert quality that meet your demands with this base.



AngularJS Hosting Europe - HostForLIFE.eu :: Feature-Rich User Management System in Angular

clock March 12, 2025 08:56 by author Peter

In this comprehensive guide, we'll build a modern User Management System using Angular. This application showcases several advanced features including theme switching, undo/redo functionality, and robust data management.


Table of Contents

Project Overview

  • Architecture and Design
  • Core Features Implementation
  • Services Implementation
  • Component Development
  • Advanced Features
  • Best Practices and Tips

Project Overview
Our User Management System includes the following features.

  • Modern, responsive design with light/dark theme support
  • Interactive data grid with in-place editing
  • Complete CRUD operations with validation
  • Undo/Redo functionality
  • Data export capabilities (Excel, PDF, PNG)
  • Loading placeholders
  • Enhanced user experience with tooltips
  • Form validation with error messages
  • Duplicate entry prevention
  • Confirmation dialogs
  • Local storage persistence
  • Reusable components

Architecture and Design
Project Structure
src/
├── app/
│   ├── components/
│   │   ├── user-grid/
│   │   └── shared/
│   ├── services/
│   │   ├── user.service.ts
│   │   ├── theme.service.ts
│   │   └── data.service.ts
│   └── models/
│       └── user.model.ts

Core Models

// user.model.ts
export interface User {
    id?: number;
    name: string;
    userName: string;
    email: string;
    phone: string;
    website: string;
}
export interface UserState {
    users: User[];
    undoStack: User[][];
    redoStack: User[][];
}


Core Features Implementation
Theme Service: The theme service manages application-wide theme switching.
    @Injectable({
      providedIn: 'root'
    })
    export class ThemeService {
      private isDarkTheme = new BehaviorSubject<boolean>(false);
      isDarkTheme$ = this.isDarkTheme.asObservable();
      constructor() {
        const savedTheme = localStorage.getItem('theme');
        if (savedTheme) {
          this.setDarkTheme(savedTheme === 'dark');
        }
      }
      setDarkTheme(isDark: boolean) {
        this.isDarkTheme.next(isDark);
        localStorage.setItem('theme', isDark ? 'dark' : 'light');
        document.body.classList.toggle('dark-theme', isDark);
      }
    }


User Service: The User Service handles all data operations with undo/redo support.
@Injectable({
  providedIn: 'root'
})
export class UserService {
  private readonly STORAGE_KEY = 'user_data';

  private state: UserState = {
    users: [],
    undoStack: [],
    redoStack: []
  };

  private usersSubject = new BehaviorSubject<User[]>([]);
  users$ = this.usersSubject.asObservable();

  // CRUD Operations with Undo/Redo Support
  addUser(user: User): boolean {
    if (this.isDuplicate(user)) return false;

    this.pushToUndoStack();
    user.id = this.getNextId();
    this.state.users.push(user);
    this.updateState();

    return true;
  }

  // Additional methods for update, delete, undo, redo
}

Data Grid Component: The main grid component implements the user interface.
@Component({
  selector: 'app-user-grid',
  template: `
    <div class="user-grid-container" [class.dark-theme]="isDarkTheme$ | async">
      <div class="header">
        <h2>User Management</h2>
        <div class="actions">
          <app-button (click)="addNewUser()">Add User</app-button>
          <app-button (click)="toggleTheme()">Toggle Theme</app-button>
        </div>
      </div>
      <!-- Grid implementation -->
    </div>
  `
})
export class UserGridComponent implements OnInit {
  users$ = this.userService.getUsers();
  isDarkTheme$ = this.themeService.isDarkTheme$;

  constructor(
    private userService: UserService,
    private themeService: ThemeService
  ) {}
}


Advanced Features
Export Functionality

export class ExportService {
  exportToExcel(data: User[]) {
    const worksheet = XLSX.utils.json_to_sheet(data);
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, 'Users');
    XLSX.writeFile(workbook, 'users.xlsx');
  }
  // Similar methods for PDF and PNG export
}


Loading Placeholders
<ng-container *ngIf="loading$ | async; else userGrid">
  <div class="placeholder-grid">
    <div class="placeholder-row" *ngFor="let i of [1, 2, 3, 4, 5]">
      <!-- Placeholder content -->
    </div>
  </div>
</ng-container>

Form Validation
export class UserFormComponent {
  userForm = this.fb.group({
    name: ['', [Validators.required, Validators.minLength(2)]],
    email: ['', [Validators.required, Validators.email]],
    userName: ['', [Validators.required, Validators.pattern('[a-zA-Z0-9_-]*')]],
    // Additional form controls
  });
}

Tips

  • State Management
    • Use BehaviorSubject for reactive state management
    • Implement undo/redo using stack data structures
    • Persist state changes to localStorage
  • Performance Optimization
    • Use OnPush change detection strategy
    • Implement trackBy functions for ngFor loops
    • Lazy load features when possible
  • Error Handling
    • Implement comprehensive error handling
    • Use toast notifications for user feedback
    • Log errors appropriately

This User Management System demonstrates several Angular best practices and advanced features. Key takeaways include.

  • Proper service architecture for state management
  • Implementing complex features like undo/redo
  • Creating reusable components
  • Managing application themes
  • Handling data persistence
  • Form validation and error handling




AngularJS Hosting Europe - HostForLIFE.eu :: Scalable Apps' Invisible Architects of Dependency and Services

clock March 7, 2025 06:33 by author Peter

Have you ever wondered how Angular apps seem to handle intricate dependencies and logic with ease? The magic that happens behind the scenes with services and dependency injection (DI) is frequently the key. These Angular features are the unsung heroes that drive the scalability and maintainability of your app, despite the fact that they might sound like catchphrases.

In-depth discussions of services, their creation and usage, and how Angular's DI system may help you manage dependencies more efficiently will all be covered in this article. Consider this your backstage ticket to learning the architecture of Angular, where we will unravel the intricacies of DI and demonstrate how to use it to improve the modularity, testability, and—above all—efficiency of your application.

Let's get started and discover Angular's services and DI capability!

Services
For an easier understanding, we will use the analogy of a remote control.
You do not need to go up to the TV to change channels, adjust the volume, or turn it on and off. You just use the remote (service), which is always there, to perform these actions from a distance.

In Angular, services are like this remote. Components don’t need to directly handle or interact with complex logic or data—they just "press a button" (call a service method), and the service handles the rest. It simplifies things and keeps everything within easy reach.

Another important aspect is reusability. With a universal remote control, you can control multiple devices. Instead of having to adjust each device manually or have a separate remote for each, the universal remote lets you control all these devices with just one tool.

In Angular, a service is a reusable piece of code that you can "call" from any component in your app. Whether it is fetching data, performing a calculation, or handling complex logic, you don’t have to rewrite the same functionality in multiple components. You simply "press the button" (call the service) wherever it is needed, and the service does the job, ensuring that your app remains clean, DRY (Don’t Repeat Yourself), and easy to maintain.

Services maximize reusability and provide a better code structure for your project.

When to use a service?

In Angular, a service should be used when you need to:

  • Share data or logic across components: Services provide a central place where data or business logic can be shared across multiple components, so you don't have to duplicate code in each component.
  • Encapsulate business logic: If you have complex logic or operations that don’t belong directly in a component, putting them in service makes your code cleaner and more maintainable.
  • Make HTTP requests: Services are commonly used for interacting with back-end APIs. Instead of having HTTP requests directly inside components, you use services to handle all the communication with the server.
  • Manage state: Services can manage the state of your application, like keeping track of user authentication, settings, or preferences, and can keep this state consistent across components.
  • Improve testability: By separating business logic into services, you can more easily write unit tests for that logic. It also allows mocking or stubbing services in tests to isolate components.

Creating a service

  • Create your angular project
  • Create the service you need using the following command

ng generate service <<name of the service>>

If you need to create the service in a specific file, you can modify the command slightly as follows:
ng generate service <<folder-path/name_of_the_service>>

For our demo, we will create our service in the ‘my-services-folder’ folder and call it ‘my-first-service’. We will use this command: ng generate service my-services-folder/my-first-service

Once the command is successfully executed, you will see the following:

The project will include the following:

When you open the service.ts file, you will see the following file already created for you:

You can start writing your functions under the constructor [as from line 9].

Note. A common practice is to have API calls in services. For the sake of simplicity, a function returning a welcome note is used in our example.

At this point, we have successfully created a service.

Inject and use a service

Think of it like this: if you are building a car, instead of the car having to make its own engine, wheels, and seats, someone (like a supplier) gives those parts to the car so it can assemble them.

Dependency injection (DI) is a way to provide components with access to services and other resources.

In our example, we will inject our ‘MyFirstService’ in the ‘app component’ and use the getGreeting function. This will allow us to reuse the function. Remember that, the same steps apply to any component you would want to inject the service.

Open the component you want to inject the service and pass the service in the constructor.
In our case, we will do it in the app.component.ts

Create a function that will call the required function from the service. You can also call it from the ngOnInit as well. It all depends on your use cases.

Create the html template to get and display the information and trigger the function call.
In our case, we will create in the app.component.html

Note. On line 2, we are using [(ngModel)]. This is called 2-way binding.
When using it, you need to add ‘FormsModule’ under ‘imports’ in the component’s module file; in our case, app.module.ts.


Below is what the end result looks like:


Conclusion
Angular's core ideas of services and dependency injection aid in organizing your application in a way that is modular, testable, and manageable. Dependency injection makes sure that these services are delivered in a flexible and effective way, while services allow you to encapsulate reusable functionality and communicate data across many components. Correct DI setup enables loose connection between services and components, which facilitates code scalability and modification as your application expands. You can write code that follows best practices in Angular development and is cleaner and easier to maintain if you have a firm grasp of services and DI.



AngularJS Hosting Europe - HostForLIFE.eu :: Knowing the Hooks of the Angular Lifecycle

clock February 11, 2025 07:30 by author Peter

Components in Angular traverse through several phases, from creation to decomposition. The angular lifespan is the aggregate term for these phases. Knowing how to use Angular's lifecycle hooks enables developers to conduct initialization, cleanup, and other essential tasks at strategic points in a component's lifespan. An extensive examination of each of these lifecycle hooks and their proper application is given in this article.

Angular Lifecycle Hooks
Here is a list of the primary lifecycle hooks in Angular.

  • ngOnChanges
  • ngOnInit
  • ngDoCheck
  • ngAfterContentInit
  • ngAfterContentChecked
  • ngAfterViewInit
  • ngAfterViewChecked
  • ngOnDestroy


Let's explore each of these hooks in detail.

1. ngOnChanges
When it’s called: This hook is called whenever an input property bound to a component changes. It’s called before ngOnInit and whenever the input properties are updated.
Use case: Use ngOnChanges to act on changes to input properties from the parent component.

Example
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-child',
template: '<p>{{data}}</p>',
})
export class ChildComponent implements OnChanges {
@Input() data: string;
ngOnChanges(changes: SimpleChanges) {
console.log('ngOnChanges called', changes);
}
}


2. ngOnInit
When it’s called: This hook is called once, after the first ngOnChanges. It’s typically used for component initialization.
Use case: Use ngOnInit to perform component initialization, like fetching data.

Example
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-example',
template: '<p>Example works!</p>',
})
export class ExampleComponent implements OnInit {
ngOnInit() {
console.log('ngOnInit called');
}
}


3. ngDoCheck
When it’s called: This hook is called during every change detection run. It’s useful for custom change detection.
Use case: Use ngDoCheck to implement custom change detection logic.

Example
import { Component, DoCheck } from '@angular/core';
@Component({
selector: 'app-check',
template: '<p>Check works!</p>',
})
export class CheckComponent implements DoCheck {
ngDoCheck() {
console.log('ngDoCheck called');
}
}


4. ngAfterContentInit
When it’s called: This hook is called after Angular projects external content into the component’s view (ng-content).
Use case: Use ngAfterContentInit to respond to content projection into the component.

Example
import { Component, AfterContentInit } from '@angular/core';
@Component({
selector: 'app-content',
template: '<ng-content></ng-content>',
})
export class ContentComponent implements AfterContentInit {
ngAfterContentInit() {
console.log('ngAfterContentInit called');
}
}


5. ngAfterContentChecked
When it’s called: This hook is called after every check of the component’s projected content.
Use case: Use ngAfterContentChecked to act after the content is checked.

Example
import { Component, AfterContentChecked } from '@angular/core';
@Component({
selector: 'app-content-check',
template: '<ng-content></ng-content>',
})
export class ContentCheckComponent implements AfterContentChecked {
ngAfterContentChecked() {
console.log('ngAfterContentChecked called');
}
}


6. ngAfterViewInit
When it’s called: This hook is called after Angular initializes the component’s views and child views.
Use case: Use ngAfterViewInit to perform actions after the component’s view is initialized.

Example
import { Component, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-view-init',
template: '<p>View Init works!</p>',
})
export class ViewInitComponent implements AfterViewInit {
ngAfterViewInit() {
console.log('ngAfterViewInit called');
}
}


7. ngAfterViewChecked
When it’s called: This hook is called after every check of the component’s view.
Use case: Use ngAfterViewChecked to act after the component’s view is checked.

Example
import { Component, AfterViewChecked } from '@angular/core';
@Component({
selector: 'app-view-check',
template: '<p>View Check works!</p>',
})
export class ViewCheckComponent implements AfterViewChecked {
ngAfterViewChecked() {
console.log('ngAfterViewChecked called');
}
}


8. ngOnDestroy
When it’s called: This hook is called just before Angular destroys the component.
Use case: Use ngOnDestroy for cleanup, like unsubscribing from observables and detaching event handlers.

Example
import { Component, OnDestroy } from '@angular/core';
@Component({
selector: 'app-destroy',
template: '<p>Destroy works!</p>',
})
export class DestroyComponent implements OnDestroy {
ngOnDestroy() {
console.log('ngOnDestroy called');
}
}

Lifecycle Sequence
Understanding the order of these hooks is crucial for correctly implementing logic that depends on the component's lifecycle stages.

  • ngOnChanges
  • ngOnInit
  • ngDoCheck
  • ngAfterContentInit
  • ngAfterContentChecked
  • ngAfterViewInit
  • ngAfterViewChecked
  • ngOnDestroy


Practical Use Cases

  • ngOnInit: Ideal for initialization tasks like fetching data from a service.
  • ngOnChanges: Useful for reacting to changes in input properties.
  • ngOnDestroy: Perfect for cleaning up resources, like unsubscribing from observables.
  • ngAfterViewInit: Great for manipulating the DOM after the view is initialized.

Conclusion
Mastering Angular lifecycle hooks is essential for building robust and maintainable Angular applications. By understanding when and how to use each hook, developers can ensure their components are properly initialized, updated, and cleaned up. This not only leads to better performance but also improves code organization and readability.



AngularJS Hosting Europe - HostForLIFE.eu :: Error handling in Angular

clock January 20, 2025 06:34 by author Peter

In order to guarantee a seamless user experience and facilitate debugging, Angular error management entails recording and controlling errors. Angular comes with a number of built-in tools and methods for methodically managing faults.


1. Managing Errors in HTTP

The HttpClient and the catchError operator from RxJS can be used to manage errors in HTTP requests.

For instance
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';

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

  constructor(private http: HttpClient) {}

  getData() {
    return this.http.get(this.apiUrl).pipe(
      catchError(this.handleError)
    );
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // Client-side error
      console.error('Client-side error:', error.error.message);
    } else {
      // Server-side error
      console.error(`Server-side error: ${error.status} - ${error.message}`);
    }
    return throwError(() => new Error('Something went wrong; please try again later.'));
  }
}


catchError: Intercepts errors and allows custom handling.
throwError: Re-throws the error after handling it for further processing.

2. Global Error Handling
Angular provides a way to handle application-wide errors using the ErrorHandler service.

Custom Global Error Handler.
Create a custom error handler.
import { ErrorHandler, Injectable, NgZone } from '@angular/core';

  @Injectable()
  export class GlobalErrorHandler implements ErrorHandler {
    constructor(private ngZone: NgZone) {}

    handleError(error: any): void {
      // Log the error to the console or a logging service
      console.error('Global Error:', error);

      // Notify the user (e.g., using a toast or modal)
      this.ngZone.run(() => {
        alert('An unexpected error occurred.');
      });
    }
  }
   

Register the error handler in AppModule.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { GlobalErrorHandler } from './global-error-handler';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [{ provide: ErrorHandler, useClass: GlobalErrorHandler }],
  bootstrap: [AppComponent]
})
export class AppModule {}

The service will be used throughout the entire application to catch logs.

3. Error Interceptors
Use an HTTP interceptor to handle errors globally for all HTTP requests.

Example

Create an HTTP interceptor.
  import { Injectable } from '@angular/core';
  import { HttpInterceptor, HttpRequest, HttpHandler, HttpErrorResponse } from '@angular/common/http';
  import { catchError, throwError } from 'rxjs';

  @Injectable()
  export class ErrorInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler) {
      return next.handle(req).pipe(
        catchError((error: HttpErrorResponse) => {
          // Handle different error types
          if (error.status === 404) {
            console.error('Not Found:', error.message);
          } else if (error.status === 500) {
            console.error('Server Error:', error.message);
          }

          // Optionally, rethrow the error
          return throwError(() => new Error('An error occurred. Please try again later.'));
        })
      );
    }
  }
   

Register the interceptor in AppModule.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { ErrorInterceptor } from './error-interceptor';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ErrorInterceptor,
      multi: true
    }
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule {}

4. Using Angular Guards for Route Error Handling
Angular guards can protect routes and handle access-related errors.
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}

  canActivate(): boolean {
    const isAuthenticated = false; // Replace with actual authentication logic
    if (!isAuthenticated) {
      alert('You are not authorized to access this page.');
      this.router.navigate(['/login']);
      return false;
    }
    return true;
  }
}


5. Error Display in the UI
Display user-friendly error messages in the UI using Angular components.

Example
Create an error message component.
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-error-message',
  template: `
    <div *ngIf="errorMessage" class="error">
      {{ errorMessage }}
    </div>
  `,
  styles: [
    `
      .error {
        color: red;
      }
    `,
  ],
})
export class ErrorMessageComponent {
  @Input() errorMessage: string | null = null;
}



Use the component.
<app-error-message [errorMessage]="error"></app-error-message>

6. RxJS Error Handling Strategies
    Retry Failed Requests.
    import { retry } from 'rxjs';

    this.http.get(this.apiUrl).pipe(
      retry(3), // Retry up to 3 times
      catchError(this.handleError)
    );

Fallback Data.
this.http.get(this.apiUrl).pipe(
  catchError(() => of([])) // Return fallback data on error
);


7. Logging Errors
Use external services like Sentry, LogRocket, or custom logging services to log errors.

Example
@Injectable({
  providedIn: 'root'
})
export class LoggingService {
  logError(message: string, stack: string) {
    // Send error logs to an external server
    console.log('Logging error:', message);
  }
}




AngularJS Hosting Europe - HostForLIFE.eu :: Using Pipes to Create Clear and Effective Angular Applications

clock January 8, 2025 06:45 by author Peter

The use of "pipes" is one of Angular's most potent tools for formatting and changing data inside templates. Developers can apply transformations like formatting dates, changing text cases, or even filtering data in an efficient and reusable way by using pipes, which offer a declarative mechanism to handle data before it is shown to the user.

Writing clean, manageable, and modular code for Angular applications requires an understanding of pipes. The main distinctions between pipes and functions will be discussed in this post, along with how to use built-in pipes and make your own custom pipes to increase Angular's functionality. You will have a firm grasp on how to integrate pipes into your Angular projects to improve user experience and expedite data presentation by the end of this tutorial.

What is an Angular Pipe?
In Angular, a pipe is a way to transform data before it is displayed in the user interface. Pipes can be used in templates to modify or format data without having to alter the original data.

Pipes are an Angular concept, not a TypeScript (TS) feature. They are a core part of Angular’s template syntax and are used to transform data in the view (template) layer of Angular applications.

Key Points about Pipes in Angular

  • Angular-Specific: Pipes are a built-in feature of the Angular framework designed to be used in Angular templates. They are not a native feature of JavaScript or TypeScript.
  • Purpose: Their primary function is to transform data in the template before it is displayed to the user. This transformation can include formatting dates, numbers, currencies, filtering arrays, or performing more complex data transformations.

Declarative Transformation: Pipes enable declarative transformation of data within the template, meaning that the logic for transforming data is cleanly abstracted away from the component’s TypeScript code.

You may be wondering why we should use Pipes when we can use functions.

Criteria Pipe Function
Purpose Data transformation in the template Business logic and calculations
Use case Formatting, filtering, sorting, etc. Complex or multi-step calculations
Performance Pure pipes are efficient for transforming data only when needed Functions can be less performant when used in templates (requires manual calls)
Reusability Highly reusable across templates Functions are reusable within the component or service
Asynchronous Handling Handles observables and promises with AsyncPipe Requires manual subscription logic or use of 'async' in templates
Complexity Best for simple, declarative transformations Best for complex or dynamic logic
When to use When transforming data for display in the template When performing business logic or side effects that don't belong in the template

Types of Pipes
There are two types of Pipes.
Pure Pipe (Default): A pure pipe will only re-run when its input value changes.
    @Pipe({

      name: 'pureExample',

      pure: true // This is the default value, so you can omit this

    })

    export class PureExamplePipe implements PipeTransform {

      transform(value: any): any {

        console.log('Pure pipe executed');

        return value;

      }

    }


Impure Pipe: An impure pipe will re-run whenever Angular detects a change in the component’s state, even if the input value hasn’t changed.
@Pipe({
  name: 'impureExample',
  pure: false // Set to false to make it impure
})
export class ImpureExamplePipe implements PipeTransform {
  transform(value: any): any {
    console.log('Impure pipe executed');
    return value;
  }
}


In Angular, you can use in-built pipes or create your own.

In-built pipes

Angular provides some basic pipes that can be used.
It comes from the '@angular/common' package.

Some popular ones that can be helpful are:
CurrencyPipe, DatePipe, DecimalPipe, LowerCasePipe, UpperCasePipe and TitleCasePipe

How to use an in-built pipe?

In your ts file, define your variable. In our example, we will use the variable title.
title = 'app works!';

In your html, you can use the pipe as follows:
<h1> {{title | uppercase}} </h1>

The result is how the string title is displayed:

Note. The currency ‘USD’ is added in front because of the currency pipe, and only 10 characters are displayed because of the slide pipe.

Custom pipes

Run the command below to create a pipe file:
ng generate pipe <<pipe-name>>.

For example: ng generate pipe my-custom-pipe. Once executed, the two files below will be created

Open the file ‘my-custom-pipe.pipe.ts. You will see the following boilerplate code provided:
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'myCustomPipe'
})
export class MyCustomPipePipe implements PipeTransform {
  transform(value: any, args?: any): any {
    return null;
  }
}


After the default class, you can create the function for your new pipe. In our case, we will create a pipe that will replace spaces in a hyphen. It is important to add the decorator ‘@Pipe’ before the class so that Angular knows what follows will be a pipe. Also, pass the name of the pipe as a parameter in the ‘@Pipe’ decorator. Also, when creating the class, implement ‘PipeTransform’. The resulting class will be as follows:
@Pipe({name: 'removeWhiteSpace'})
export class RemoveWhiteSpacePipe implements PipeTransform {
  transform(value: string): string {
    return value.replace(/\s+/g, '-');
  }
}


The resulting class will be as follows (the full code):
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'myCustomPipe'
})
export class MyCustomPipePipe implements PipeTransform {


  transform(value: any, args?: any): any {
    return null;
  }
}

@Pipe({name: 'removeWhiteSpace'})
export class RemoveWhiteSpacePipe implements PipeTransform {
  transform(value: string): string {
    return value.replace(/\s+/g, '-');
  }
}


In the ts file of your component, create the variable that will hold the value that will be transformed
textWithSpaces = 'This is a text with a lot of spaces that will be transformed';

In the html file of your component, do the following:
<p>{{ textWithSpaces | removeWhiteSpace }}</p>

The result is the following:

Conclusion
An effective and potent method for formatting and transforming data in the templates of your application is to use angular pipes. Without having to code repetitious logic in your components, you can quickly manipulate data types like strings, dates, and numbers by utilizing built-in pipes. More freedom is provided by custom pipelines, which let you design modular, reusable, and maintainable transformation logic that is suited to your particular requirements.

Understanding the distinction between pipes and functions is key to leveraging their full potential. While functions provide a direct way to execute code, pipes offer a declarative approach to handle transformations directly within templates, improving readability and performance.

Whether you're working with Angular’s built-in pipes or creating custom ones, the ability to easily manipulate data in the view layer is a significant advantage in building dynamic and user-friendly applications. By mastering Angular pipes, you can keep your code clean, concise, and aligned with best practices, ultimately leading to more maintainable and scalable applications.

With the knowledge of how to use and create pipes, you’re now equipped to enhance your Angular applications with powerful data transformations, making your development experience more efficient and enjoyable.



AngularJS Hosting Europe - HostForLIFE.eu :: In VS Code by Debuggers for Chrome New

clock December 16, 2024 06:13 by author Peter

More than four years ago, on January 15, 2021, Debugger for Chrome wrote the post Debug Angular (1) in VS Code. With 208K readings, that article is really popular. This indicated that while Angular is a very popular tool for web development, it is difficult to persuade many developers to focus on its devugging technology. Although this article is a supplement to the original, I am aware that the Visual Studio Code Debugger for Chrome has been deprecated. We'll check if the original article is still relevant or valid.

Debug Angular in VS Code
Now the current VS Code is with v1.95.3. The VS Code Debugger for Chrome is deprecated:

However, user is introduced to use the JavaScript Debugger extension instead. Click the link, we can see that

this extension is already installed if you use VS Code or Visual Studio:

And furthermore,

Nightly Extension you may want to install our nightly build to get the latest fixes and features. The nightly build runs at 5PM PST on each day that there are changes. You can get Nightly extension from JavaScript Debugger (Nightly) - Visual Studio Marketplace, or search in VS Code extension for
@id:ms-vscode.js-debug-nightly

Debugging in VS Code

Actually, the debugging is the same way as we introduced in article, Debug Angular (1), In VS Code by Debugger for Chrome, we will not repeat the process, but given here the major points:

Configure the launch.json file --- choose the port as your app running

Run the app from the Angular CLI (very important, otherwise, the debugging will not work) by command.
Start to debug.



AngularJS Hosting Europe - HostForLIFE.eu :: Create Customer and Process Card Payments with Tokenization Angular

clock December 11, 2024 06:41 by author Peter

In the following article we will be looking into credit card payment and credit card design using the Stripe element. Stripe is one of the online and in-person payment processing systems. It's not open source. In the below sample, we are using the test version. We can do operations like creating a customer, payment, custom UI element design using the Stripe card element, etc.

In the below sample, I have used the stripe.js npm package for Stripe payment processing in Angular and Stripe REST APIs. And for business logic, I have used the dot net in this article. I have defined back-end functionality.

Package Installation
Use the below npm command to install Stripe in Angular.
npm install @stripe/stripe-js

Create a customer using card tokenization
Creates a new customer in the Stripe system, associating a payment source; basically, it will create a customer after getting card info from users.

In this sample I am using a modal popup to get the card info.

creditcard.component.html
In the below sample, we are loading the card number, expiry date and CVV using stripe element.
<div class="modal-body">
    <!-- Modal content area -->
    <h2>Enter Your Credit Card Information</h2>
    <!-- Title -->

    <form (ngSubmit)="onSubmit()">
        <!-- Angular form submission handler -->

        <!-- Card Number -->
        <label for="card-number">Card Number</label>
        <!-- Label for card number -->
        <div id="card-number" class="stripe-element"></div>
        <!-- Stripe Element placeholder for Card Number -->

        <!-- Expiry Date -->
        <label for="card-expiry">Expiration Date</label>
        <!-- Label for expiration date -->
        <div id="card-expiry" class="stripe-element"></div>
        <!-- Stripe Element placeholder for Expiration Date -->

        <!-- CVC -->
        <label for="card-cvc">CVC</label>
        <!-- Label for CVC -->
        <div id="card-cvc" class="stripe-element"></div>
        <!-- Stripe Element placeholder for CVC -->

        <!-- Errors -->
        <div id="card-errors" role="alert"></div>
        <!-- Placeholder to show card input errors -->

        <!-- Buttons -->
        <div class="modal-buttons">
            <!-- Button container -->
            <button type="button" (click)="vm.closeCardModal()">Cancel</button>
            <!-- Button to cancel the modal -->
            <button type="submit" [disabled]="isSubmitting">Submit</button>
            <!-- Submit button, disabled while `isSubmitting` is true -->
        </div>
    </form>
</div>

creditcard.component.scss
.modal-body {
    background-color: #f9f9f9;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    max-width: 400px;
    margin: auto;
}
.modal-content {
  background: #fff;
  padding: 20px;
  border-radius: 8px;
  width: 400px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  text-align: center;
}

.modal-buttons {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
}

.requiredIndicate {
color: red; font-weight: bold;
}
h2 {
    text-align: center;
    color: #333;
}

form {
    display: flex;
    flex-direction: column;
}
.error-message{
    font-size: 15px;
    color: #df0707;
    padding: 2px;
}
label {
    margin-top: 10px;
    color: #555;
}

input {
    padding: 10px;
    margin-top: 5px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

button {
    margin-top: 20px;
    padding: 10px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

button[type="submit"] {
    background-color: #28a745;
    color: white;
}

button[type="button"] {
    background-color: #dc3545;
    color: white;
}

button:hover {
    opacity: 0.9;
}
#card-errors {
  color: red;
  font-size: 0.9em;
  margin-top: 5px;
}

creditcard.component.ts
In Component, we are loading the Stripe element to our UI element using Id. Don't hardcode the publish and secret key in the front end. try to get from backend
The mount() method is used to attach a Stripe Element (such as cardNumber, cardExpiry, or cardCvc) to a specific DOM element in your HTML.
Each Stripe Element can only be mounted to one DOM element. If you need to move it to a new location, unmount it first using the unmount() method
loadStripe() The method is used to create an instance of the stripe using the publish key.
displayError() method used to bind the error message to an HTML element.
onSubmit() method used for form submission and external validation if need means.
the createToken method in Stripe.js is used to securely create a single-use token that represents a payment method (like a credit card). This token can then be sent to your server for processing a payment or saving card details

import { AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core';
import { loadStripe, Stripe, StripeCardCvcElement, StripeCardExpiryElement, StripeCardNumberElement, StripeElements } from '@stripe/stripe-js';

@Component({
  selector: 'app-creditcard',
  templateUrl: './creditcard.component.html', // HTML template for the component
  styleUrls: ['./creditcard.component.scss'] // SCSS styles for the component
})
export class CreditcardComponent implements OnInit, AfterViewInit {

  // Stripe API keys (publishable key for client-side usage)
  publishKey: string = ""; // Please replace your stripe publish key here.

  // Stripe objects
  stripe: Stripe | null = null; // Stripe instance
  elements: StripeElements | null = null; // Stripe Elements instance
  cardNumber: StripeCardNumberElement | null = null; // Card Number Element
  cardExpiry: StripeCardExpiryElement | null = null; // Expiry Date Element
  cardCvc: StripeCardCvcElement | null = null; // CVC Element

  isSubmitting: boolean = false; // Prevent duplicate submissions

  @Output() tokenGenerated = new EventEmitter<string>(); // Emit the generated token to parent component

  constructor() { }

  ngOnInit(): void {
    // Component initialization logic (if needed)
  }

  async ngAfterViewInit() {
    // Load the Stripe instance with the publishable key
    this.stripe = await loadStripe(this.publishKey);

    if (this.stripe) {
      // Initialize Stripe Elements
      this.elements = this.stripe.elements();

      // Define the custom styles for Stripe Elements
      const style = {
        base: {
          color: '#32325d', // Default text color
          fontFamily: '"Roboto", sans-serif', // Font family
          fontSize: '16px', // Font size
          '::placeholder': {
            color: '#aab7c4', // Placeholder text color
          },
        },
        invalid: {
          color: '#fa755a', // Invalid input text color
          iconColor: '#fa755a', // Invalid icon color
        },
      };

      // Create and mount the Card Number Element
      this.cardNumber = this.elements.create('cardNumber', { style });
      this.cardNumber.mount('#card-number');

      // Create and mount the Expiry Date Element
      this.cardExpiry = this.elements.create('cardExpiry', { style });
      this.cardExpiry.mount('#card-expiry');

      // Create and mount the CVC Element
      this.cardCvc = this.elements.create('cardCvc', { style });
      this.cardCvc.mount('#card-cvc');

      // Attach event listeners to handle real-time validation errors
      this.cardNumber.on('change', this.displayError.bind(this));
      this.cardExpiry.on('change', this.displayError.bind(this));
      this.cardCvc.on('change', this.displayError.bind(this));
    }
  }

  displayError(event: any) {
    // Handle real-time validation errors
    const displayError = document.getElementById('card-errors');
    if (displayError) {
      if (event.error) {
        displayError.textContent = event.error.message; // Show error message
      } else {
        displayError.textContent = ''; // Clear error message
      }
    }
  }

  async onSubmit() {
    // Ensure Stripe and card elements are initialized
    if (!this.stripe || !this.cardNumber) {
      console.error('Stripe or card elements not initialized');
      return;
    }

    this.isSubmitting = true; // Set submission state to true

    // Create a token for the card details
    const { token, error } = await this.stripe.createToken(this.cardNumber);

    if (error) {
      // Display error message if tokenization fails
      const displayError = document.getElementById('card-errors');
      if (displayError && error.message) {
        displayError.textContent = error.message;
      }
    } else {
      // Emit the token to the parent component
      this.tokenGenerated.emit(token?.id);

      // Close the modal (assumes `vm.closeCardModal()` is defined)
      this.vm.closeCardModal();
    }

    this.isSubmitting = false; // Reset submission state
  }
}


Sample Output of UI Element

 

Modal Popup Open/Close
ngx-bootstrap npm package was used to open and close the modal. I have used a modal popup in the payment component to open and close the modal, so I have included it in payment.module.ts and declartion included.

package installation
npm i ngx-bootstrap

Include ModalModule in required module like below,

payment.module.ts

@NgModule({
  declarations: [PaymentComponent,CreditcardComponent],
  imports: [
    CommonModule,ReactiveFormsModule,FormsModule,NgToggleModule,
    PaymentRoutingModule, ModalModule.forRoot(),
  ]
})


payment.component.ts

Used to generate customer from the emitted output function creditcard.component.ts it will trigger customer generation.
openCardModal(): Used to open the modal popup with creditcard.component.ts.

constructor(private modalService: BsModalService,private paymentService:PaymentService)
  {

  }
openCardModal = () => {
  // Opens the CreditcardComponent as a modal
  this.modalRef = this.modalService.show(CreditcardComponent, {
    animated: true, // Enables animation for the modal
    keyboard: false, // Disables closing the modal via the ESC key
    backdrop: 'static', // Prevents clicking outside the modal to close it
    initialState: {
      vm: this.vm, // Passes a custom `vm` object to the modal component's input
    },
  });

  // Subscribes to the `tokenGenerated` EventEmitter from the modal component
  this.modalRef.content.tokenGenerated.subscribe((token: string) => {
    this.createCustomerForToken(token); // Calls the function to handle token processing
  });
};

createCustomerForToken(token) method
This method processes the token emitted by the modal and uses it to create a customer by calling the paymentService
createCustomerForToken(token: string) {
  // Calls the `createCustomer` method in the payment service
  this.paymentService
    .createCustomer(
      this.paymentService.GenerateCustomer(
        this.cardDetails.Name, // Name of the cardholder
        this.cardDetails.CardNumber, // Card number (masked or partial if passed)
        token // Token generated from Stripe
      )
    )
    .subscribe((data: any) => {
      // You can handle the response here, e.g., show success messages
    });
}


closeCardModal Method
This method closes the modal when the operation is complete or canceled.
closeCardModal = () => {
  this.modalRef?.hide(); // Hides the modal if `modalRef` exists
};

OnInit Method
This method used to initialize the vm's property and method
ngOnInit(): void {
  this.vm = {
    cardHolderName: null,  // Placeholder for the cardholder's name input field (starts with null)
    cardNumber: null,      // Placeholder for the credit card number (starts with null)
    expiryDate: null,      // Placeholder for the card's expiry date (starts with null)
    cvv: null,             // Placeholder for the CVV input field (starts with null)

    // Methods to open and close the card modal
    openCardModal: this.openCardModal,  // Reference to the method that opens the credit card modal
    closeCardModal: this.closeCardModal, // Reference to the method that closes the modal
  };
}


payment.service.ts
Generate an object using the generate customer, and for REST APIs, I am using the previous article.  Create customers with a tokenization block to generate customers based on tokens.
var apiUrl = ""; should be end point of the service.
 return {
    Email: Email,                     // Use the passed 'Email' parameter
    Name: Name,                       // Use the passed 'Name' parameter
    Phone: "1234567890",               // Replace with the actual phone number if needed
    Description: "Customer for test",  // Replace with an appropriate description if needed
    SourceID: token                    // The token passed from payment process
  };

public createCustomer(customer: any) {
  return this.apiHandler.post(apiUrl + "/create-customer-with-tokenisation", customer);
}
 public GeneratePaymentIntent(amount:number,offSession:boolean,paymentMethodID:string)
  {
    return {
      amount:amount,
      paymentMethodID: paymentMethodID,
      customerId: null,
      offSession:offSession
    };
  }
  public createPaymentIntent(paymentMethod:any)
  {
    return this.apiHandler.post(apiUrl + "/create-payment-intent",paymentMethod);
  }
  public createPayment(amount:any)
  {
    return this.apiHandler.post(apiUrl + "/create-payment-intent-without-paymentmethod",{ amount: amount });
  }

Pay using a card with the customer and payment method
Payment template used to pay amount using credit card.
<ng-template #paymentTemplate>
  <div class="modal-header">
    <h3 class="modal-title">Payment Information</h3>
  </div>
  <div class="modal-body">
    <div class="modal-content">
      <form (submit)="handlePayment($event)">
        <label>
          Without PaymentMethod:
          <ng-toggle  [(ngModel)]="isPaymentMethod" name="toggle" required />
        </label>
        <label>
          Card Holder Name:
          <input type="text" [(ngModel)]="cardHolderName" name="cardHolderName" required />
        </label>
        <label>
          Amount:
          <input type="text" [(ngModel)]="amount" name="amount" required />
        </label>
        <div id="pay-card-element" class="card-element"><!-- Stripe Card Element --></div>
        <div id="pay-card-errors" role="alert"></div>
        <div class="pay-modal-buttons">
          <button type="button" class="btn btn-failure" (click)="closeCardModal()">Cancel</button>
          <button type="submit" class="btn btn-success" [disabled]="loading">Pay {{ amount | currency }}</button>
        </div>
      </form>
    </div>
  </div>
</ng-template>

payment.component.ts
In payment component, variable declaration and constructor, Don't hard code publish key and secret key in html side try to get from server side.
// Declaring a dynamic object for storing values related to the modal and card details.
vm: any = {};

// Secret key for server-side Stripe API communication (should not be exposed on the client-side).
secretKey: string = "";

// Publishable key used for client-side Stripe communication.
publishKey: string = "";

// ViewChild decorator for referencing the modal template.
@ViewChild('paymentTemplate') paymentModal!: TemplateRef<any>;

// The modal reference to control its visibility and behavior.
modalRef!: BsModalRef;

// Stripe object initialized for Stripe.js integration (holds the Stripe instance).
private stripe: Stripe | null = null;

// The card element used for securely collecting card details.
private cardElement: any;

// States for loading, error messages, and cardholder's name.
public loading = false;
public error = '';
public cardHolderName = '';

// Amount to be processed for payment.
public amount = 1;

// A flag to track if it's a payment method or another type of payment action.
isPaymentMethod: boolean = true;

// Constructor injecting modal service for modal control and payment service for backend communication.
constructor(private modalService: BsModalService, private paymentService: PaymentService) {}

// ngOnInit lifecycle hook, currently not used but reserved for any initialization logic.
ngOnInit(): void {
  // Any initialization logic goes here if needed.
}


loadStripe() method

This method initializes Stripe Elements for securely collecting payment details.

  • loadStripe(this.publishKey): Asynchronously loads Stripe.js with the public key for client-side operations.
  • this.stripe.elements(): Creates an instance of Stripe Elements which provides a secure method to collect card information.
  • this.cardElement.mount('#pay-card-element'): Mounts the cardElement (a Stripe UI element) into the DOM, allowing users to enter credit card information.

async loadStripe() {
  // Load Stripe.js with the public key for client-side operations.
  this.stripe = await loadStripe(this.publishKey);

  // Check if Stripe was successfully initialized.
  if (this.stripe) {
    // Initialize Stripe Elements, which will be used to securely collect card details.
    const elements = this.stripe.elements();

    // Create a 'card' element for collecting card details, hiding postal code field.
    this.cardElement = elements.create('card', {
      hidePostalCode: true
    });

    // Mount the card element onto the HTML element with id 'pay-card-element' to display it on the page.
    this.cardElement.mount('#pay-card-element');
  } else {
    // Log an error if Stripe is not initialized properly.
    console.error('Stripe is not initialized');
  }
}

handlePayment() Method

This method handles the payment process, triggered when the user submits the payment form

  • event.preventDefault(): Prevents the form submission to allow custom handling via JavaScript.
  • this.paymentService.createPayment(this.amount): Calls a service to create a payment request with the backend, sending the payment amount.
  • this.stripe.confirmCardPayment(): Uses Stripe's API to confirm the payment using the card details entered by the user.
  • Error handling: Sets the error message if payment fails, and resets the loading state.
  • this.loading = false: Updates the loading state to stop the spinner once the payment process is finished.

async handlePayment(event: Event) {
  // Prevent default form submission behavior.
  event.preventDefault();

  // Set loading state to true to show a loading spinner or message.
  this.loading = true;

  // Check if Stripe is initialized before proceeding.
  if (this.stripe) {

    // Call the backend to create a payment, passing the amount to charge.
    this.paymentService.createPayment(this.amount).subscribe(async (data: any) => {

      // Ensure Stripe is initialized before proceeding with payment confirmation.
      if (this.stripe) {
        // Use Stripe to confirm the payment using the card details and secret key.
        const { error } = await this.stripe.confirmCardPayment(this.secretKey, {
          payment_method: {
            card: this.cardElement, // The card element that holds the user's card information.
            billing_details: {
              name: this.cardHolderName, // The name of the cardholder.
            },
          },
        });

        // Handle error if payment confirmation fails.
        if (error) {
          this.error = error.message ? error.message : 'An unknown error occurred';
          this.loading = false; // Reset loading state.
        } else {
          this.error = ''; // Clear any previous errors.
          this.loading = false; // Reset loading state.
        }
      } else {
        // Error handling if Stripe is not initialized.
        this.error = 'Stripe is not initialized';
        this.loading = false; // Reset loading state.
      }
    });

  } else {
    // Error handling if Stripe is not initialized.
    this.error = 'Stripe is not initialized';
    this.loading = false; // Reset loading state.
  }
}

openpaymentModal() Method
This method opens the payment modal where the user can enter payment details.
openpaymentModal = () => {
  // Show the modal using the BsModalService, passing the initial state with the modal data.
  this.modalRef = this.modalService.show(this.paymentModal, {
    animated: true, // Enable animations for the modal.
    keyboard: false, // Disable closing the modal via the keyboard.
    backdrop: 'static', // Prevent closing the modal by clicking outside.
    initialState: {
      vm: this.vm // Pass initial data (vm) to the modal component.
    }
  });

  // Call loadStripe to initialize Stripe and display the payment form inside the modal.
  this.loadStripe();
}


Full code snippet of payment.compoent.ts
import { trigger, state, style, transition, animate } from '@angular/animations';
import { Component, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { CreditcardComponent } from '../creditcard/creditcard.component';
import { PaymentService } from './payment.service';
import { loadStripe, Stripe } from '@stripe/stripe-js';

@Component({
  selector: 'app-payment',
  standalone: false,
  encapsulation:ViewEncapsulation.None,
  templateUrl: './payment.component.html',
  styleUrl: './payment.component.scss'

})
export class PaymentComponent implements OnInit{

  vm: any={};
  secretKey:string ="sk_test_51Q2TApRw3m8aFNMV8RYvLabAEmwYOwHtiNWXrP1pR88IlVeuItDrhoxbb80YcCm45BoN9ncle0Mw84wv74Vc4rV700Y0gwbdh7";
  publishKey:string ="pk_test_51Q2TApRw3m8aFNMVKcnlDcs0EH3pu0EuhPpKyYe99eaBXE2ex9Nd1aSkCi4dxZQ3jBZwjlyrUAU30RN8nuGth6dv00HGMKk8vH";


  @ViewChild('paymentTemplate') paymentModal !: TemplateRef<any> ;
  modalRef!: BsModalRef;
  private stripe: Stripe | null = null;
private cardElement: any;
public loading = false;
public error = '';
public cardHolderName = '';
public amount = 1;
 isPaymentMethod:boolean =true;
  constructor(private modalService: BsModalService,private paymentService:PaymentService)
  {

  }

  ngOnInit(): void {
    this.vm = {
      cardHolderName: null,
      cardNumber: null,
      expiryDate: null,
      cvv: null,
      openCardModal: this.openCardModal,
      closeCardModal: this.closeCardModal,
    };
  }

  openCardModal=()=> {
    this.modalRef = this.modalService.show(CreditcardComponent,{ animated: true,
      keyboard: false,
      backdrop : 'static',  initialState: {
        vm: this.vm
      }});
  this.modalRef.content.tokenGenerated.subscribe((token: string) => {
  this.createCustomerForToken(token);
  });
  }
  createCustomerForToken(token: string)
  {
    this.paymentService.createCustomer(this.paymentService.GenerateCustomer('', '', token)).subscribe((data: any) => {

    });
}
  closeCardModal=()=> {
    this.modalRef?.hide();
  }

async loadStripe() {
  this.stripe = await loadStripe(this.publishKey);
  if (this.stripe) {
    const elements = this.stripe.elements();
    this.cardElement = elements.create('card', {
      hidePostalCode: true
    });
    this.cardElement.mount('#pay-card-element');
  } else {
    console.error('Stripe is not initialized');
  }
}

async handlePayment(event: Event) {
  event.preventDefault();
  this.loading = true;

  if (this.stripe) {
    if(!this.isPaymentMethod){
      const { paymentMethod, error } = await this.stripe.createPaymentMethod({
        type: 'card',
        card: this.cardElement,
        billing_details: { name: this.cardHolderName },
      });

      if (error) {
        const displayError = document.getElementById('pay-card-errors');
      if (displayError && error.message) {
        displayError.textContent = error.message;
        this.loading = false;
      }
      } else {
        // Send payment method to backend

        this.makePayment(paymentMethod.id);
      }
    }
    else
    {
      this.paymentService.createPayment(this.amount).subscribe(async (data: any) => {

      if (this.stripe) {
        const { error } = await this.stripe.confirmCardPayment(this.secretKey, {
          payment_method: {
            card: this.cardElement,
            billing_details: {
              name: this.cardHolderName,
            },
          },
        });

        if (error) {
          this.error = error.message ? error.message : 'An unknown error occurred';
          this.loading = false;
        } else {
          this.error = '';
          this.loading = false;
        }
      } else {
        this.error = 'Stripe is not initialized';
        this.loading = false;
      }
    });
    }
  } else {
    this.error = 'Stripe is not initialized';
    this.loading = false;
  }

}

makePayment(paymentMethodId: string) {
  this.paymentService.createPaymentIntent(this.paymentService.GeneratePaymentIntent(this.amount, false, paymentMethodId)).subscribe((data: any) => {

  });
}
openpaymentModal=()=> {
  this.modalRef = this.modalService.show(this.paymentModal,{ animated: true,
    keyboard: false,
    backdrop : 'static',  initialState: {
      vm: this.vm
    }});
    this.loadStripe();
}
}


Sample UI

Conclusion
This approach enables the creation of a robust payment system integrated with Stripe, giving you the ability to scale and manage payments efficiently within your application. Attached is the sample code.



AngularJS Hosting Europe - HostForLIFE.eu :: Understanding Service, Factory, And Provider

clock December 6, 2024 07:39 by author Peter

In this section, we will endeavor to comprehend the most frequently confounding elements and usefulness of AngularJS Service, Factory, and Provider.


Basic Understanding
AngularJS Service, Factory, or Provider are all utilized for a similar reason; i.e., for making utility capacity that can be utilized all through the page with the infused capable question. Be that as it may, the way it is made and the way it is utilized are unique. Here, we should attempt to comprehend them unmistakably.

Service

Service is utilized for imparting utility capacities to the Service reference in the controller. Service is a singleton in nature, so for one's benefit, just a single case is made in the program and a similar reference is utilized all through the page. In the Service, we make work names as properties with this question.

Factory

The reason for Factory is likewise the same as Service, however for this situation, we make another protest and include works as properties of this question and toward the end, we restore this protest.

Provider

The reason for this is again the same, however, Provider yields $get work.

Presently let's endeavor to comprehend Service, Factory, and Provider by making and utilizing them.

How to make a Service, Factory, and a Provider. To start with, we have made a module.

Next is the Service creation where we have made a service utilizing the .benefit strategy. Notice that in the service, the two capacities "Hi" and "Total" have been made on "this" question.

At that point, we have made a factory strategy. Here, we have made the two capacities "Hi" and "Aggregate" on the new "factoryObject" and afterward we are restoring that protest toward the finish of the factory strategy.

In the last, we have made a provider utilizing the .provider technique in which we are restoring a protest having two capacities "Hi" and "Aggregate" to the $get work.

Create a module
var myApp = angular.module("myApp", []);

Create a service function
app.service("toDoService", function () {
    this.toDo = function () {
        return "Hi";
    };
    this.Add = function (x, y) {
        return x + y;
    };
});

Create a factory function

app.factory("toDoFactory", function () {

    var todofactoryObject = {};
    todofactoryObject.Hi = function () {
        return "Hi";
    };
    todofactoryObject.Add = function (x, y) {
        return x + y;
    };
    return todofactoryObject;
});

Create a provider function
app.provider("todoProvider", function() {
    this.$get = function() {
        return {
            Hi: function() {
                return "Hi";
            },
            Add: function(x, y) {
                return x + y;
            }
        };
    };
});

Notice that regardless of that, every one of the three has similar capacities "Hi" and "Whole" having the same usefulness, however, the method for presentation is unique. This is the real contrast between Service, Factory, and Provider.

Utilizing Service, Factory, and Provider

To utilize them basically infuse those into the controller definition and begin utilizing those references to call capacities "Hi" and "Total" characterized in them.

Beneath the code bit it is truly straightforward. We are essentially calling "Hi" and "Whole" capacities characterized by particular Service, Factory, and Provider reference objects.
myApp.controller("todoController", function($scope, todoService, todoFactory, todoProvider) {
    // service call
    $scope.todoService = function() {
        $scope.resultt = todoService.Hi();
    };
    $scope.addFunc = function() {
        $scope.result = todoService.Add(4, 5);
    };
    // factory call
    $scope.todoFactory = function() {
        $scope.result = todoFactory.Hi();
    };
    $scope.AddFactory = function() {
        $scope.result = todoFactory.Add(4, 5);
    };
    // provider call
    $scope.todoProvider = function() {
        $scope.result = todoProvider.Hi();
    };
    $scope.addProvider = function() {
        $scope.result = todoProvider.Add(4, 5);
    };
});

<div ng-app="app" ng-controller="todoController">
    <h1>Service</h1>
    <button ng-click="todoService()">hi</button>
    <button ng-click="addService()">add</button>
    <div ng-bind="result"></div>

    <h1>Factory</h1>
    <button ng-click="todoFactory()">hi f</button>
    <button ng-click="addFactory()">add f</button>
</div>

<h1>Provider</h1>
<button ng-click="todoProvider()">hi p</button>
<button ng-click="AddProvider()">add p</button>
<div ng-bind="result"></div>
</div>


Summary
There is no distinction regarding yield/usefulness between Service, Factory, and Provider, however, the distinction comes when we attempt to make them. Every one of the three has diverse methods for creation and capacity executions.



About HostForLIFE.eu

HostForLIFE.eu is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2016 Hosting, ASP.NET Core 2.2.1 Hosting, ASP.NET MVC 6 Hosting and SQL 2017 Hosting.


Tag cloud

Sign in