or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

directives.mdhttp.mdi18n.mdindex.mdlocation.mdpipes.mdplatform.mdtesting.md
tile.json

platform.mddocs/

Platform and Environment Utilities

Angular Common provides utilities for platform detection, viewport scrolling management, DOM access, and environment-specific functionality to enable universal applications that work across different execution contexts.

Capabilities

Platform Detection

Platform Identification Functions

Functions to detect the current execution environment.

/**
 * Check if running in browser environment
 * Returns true when code is executing in a web browser
 */
export function isPlatformBrowser(platformId: Object): boolean;

/**
 * Check if running in server environment
 * Returns true when code is executing on the server (SSR)
 */
export function isPlatformServer(platformId: Object): boolean;

/**
 * Check if running in web worker app environment
 * Returns true when code is executing in a web worker
 */
export function isPlatformWorkerApp(platformId: Object): boolean;

/**
 * Check if running in web worker UI environment
 * Returns true when code is executing in a web worker UI context
 */
export function isPlatformWorkerUi(platformId: Object): boolean;

Usage Examples:

import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { PLATFORM_ID } from '@angular/core';

@Injectable()
export class PlatformService {
  private isBrowser: boolean;
  private isServer: boolean;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    this.isBrowser = isPlatformBrowser(this.platformId);
    this.isServer = isPlatformServer(this.platformId);
  }

  // Browser-only operations
  accessLocalStorage(): any {
    if (this.isBrowser) {
      return localStorage.getItem('user-data');
    }
    return null;
  }

  // Server-only operations
  logServerInfo(): void {
    if (this.isServer) {
      console.log('Server-side rendering active');
    }
  }

  // Universal operations with platform-specific logic
  getCurrentUrl(): string {
    if (this.isBrowser) {
      return window.location.href;
    } else if (this.isServer) {
      // Use server-provided URL or default
      return 'https://example.com';
    }
    return '';
  }

  // Feature detection based on platform
  supportsWebGL(): boolean {
    if (!this.isBrowser) return false;
    
    try {
      const canvas = document.createElement('canvas');
      return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
    } catch {
      return false;
    }
  }
}

@Component({
  template: `
    <div *ngIf="showBrowserFeatures">
      <button (click)="saveToLocalStorage()">Save Data</button>
      <canvas #gameCanvas></canvas>
    </div>
    <div *ngIf="!showBrowserFeatures">
      <p>Browser features not available</p>
    </div>
  `
})
export class PlatformAwareComponent {
  showBrowserFeatures: boolean;

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    private platformService: PlatformService
  ) {
    this.showBrowserFeatures = isPlatformBrowser(this.platformId);
  }

  saveToLocalStorage() {
    if (isPlatformBrowser(this.platformId)) {
      localStorage.setItem('timestamp', Date.now().toString());
    }
  }

  ngAfterViewInit() {
    if (this.showBrowserFeatures && this.platformService.supportsWebGL()) {
      // Initialize WebGL canvas
      this.initializeWebGL();
    }
  }

  private initializeWebGL() {
    // WebGL initialization code
  }
}

// Service worker detection
@Injectable()
export class ServiceWorkerService {
  private isWorker: boolean;
  private isWorkerUi: boolean;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    this.isWorker = isPlatformWorkerApp(this.platformId);
    this.isWorkerUi = isPlatformWorkerUi(this.platformId);
  }

  initializeWorkerFeatures() {
    if (this.isWorker) {
      // Initialize web worker specific features
      this.setupWorkerMessageHandling();
    }

    if (this.isWorkerUi) {
      // Initialize web worker UI specific features
      this.setupWorkerUIFeatures();
    }
  }

  private setupWorkerMessageHandling() {
    // Worker message handling logic
  }

  private setupWorkerUIFeatures() {
    // Worker UI features
  }
}

// Universal service with platform detection
@Injectable()
export class UniversalHttpService {
  private baseUrl: string;

  constructor(
    private http: HttpClient,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {
    if (isPlatformServer(this.platformId)) {
      // Use absolute URL for server-side requests
      this.baseUrl = 'https://api.example.com';
    } else {
      // Use relative URL for client-side requests
      this.baseUrl = '/api';
    }
  }

  getData(): Observable<any> {
    return this.http.get(`${this.baseUrl}/data`);
  }

  // Platform-specific caching
  getCachedData(): Observable<any> {
    if (isPlatformBrowser(this.platformId)) {
      // Use browser cache/local storage
      const cached = localStorage.getItem('cached-data');
      if (cached) {
        return of(JSON.parse(cached));
      }
    }

    // Fallback to HTTP request
    return this.getData().pipe(
      tap(data => {
        if (isPlatformBrowser(this.platformId)) {
          localStorage.setItem('cached-data', JSON.stringify(data));
        }
      })
    );
  }
}

Viewport Scrolling

ViewportScroller - Scroll Management

Service for managing viewport scrolling behavior.

/**
 * Abstract service for managing viewport scrolling
 * Provides cross-platform scrolling capabilities
 */
export abstract class ViewportScroller {
  /** Set scroll offset for anchor navigation */
  abstract setOffset(offset: [number, number] | (() => [number, number])): void;

  /** Get current scroll position */
  abstract getScrollPosition(): [number, number];

  /** Scroll to specific position */
  abstract scrollToPosition(position: [number, number]): void;

  /** Scroll to element with given anchor */
  abstract scrollToAnchor(anchor: string): void;

  /** Set browser scroll restoration behavior */
  abstract setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void;
}

/**
 * Browser implementation of ViewportScroller
 * Uses window and document APIs for scrolling
 */
export class BrowserViewportScroller implements ViewportScroller {
  setOffset(offset: [number, number] | (() => [number, number])): void;
  getScrollPosition(): [number, number];
  scrollToPosition(position: [number, number]): void;
  scrollToAnchor(anchor: string): void;
  setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void;
}

/**
 * Null implementation for non-browser environments
 * Provides no-op implementations for server-side rendering
 */
export class NullViewportScroller implements ViewportScroller {
  setOffset(offset: [number, number] | (() => [number, number])): void;
  getScrollPosition(): [number, number];
  scrollToPosition(position: [number, number]): void;
  scrollToAnchor(anchor: string): void;
  setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void;
}

Usage Examples:

@Injectable()
export class ScrollService {
  constructor(private viewportScroller: ViewportScroller) {}

  // Scroll to top of page
  scrollToTop() {
    this.viewportScroller.scrollToPosition([0, 0]);
  }

  // Scroll to specific coordinates
  scrollToCoordinates(x: number, y: number) {
    this.viewportScroller.scrollToPosition([x, y]);
  }

  // Scroll to anchor element
  scrollToSection(sectionId: string) {
    this.viewportScroller.scrollToAnchor(sectionId);
  }

  // Get current scroll position
  getCurrentScrollPosition(): [number, number] {
    return this.viewportScroller.getScrollPosition();
  }

  // Set offset for anchor scrolling (useful for fixed headers)
  setScrollOffset(x: number, y: number) {
    this.viewportScroller.setOffset([x, y]);
  }

  // Set dynamic offset based on header height
  setDynamicOffset() {
    this.viewportScroller.setOffset(() => {
      const header = document.querySelector('header');
      const headerHeight = header ? header.offsetHeight : 0;
      return [0, headerHeight + 20]; // Add some padding
    });
  }

  // Configure scroll restoration
  configureScrollRestoration(mode: 'auto' | 'manual') {
    this.viewportScroller.setHistoryScrollRestoration(mode);
  }

  // Smooth scroll to element
  smoothScrollToElement(elementId: string) {
    const element = document.getElementById(elementId);
    if (element) {
      element.scrollIntoView({ 
        behavior: 'smooth', 
        block: 'start',
        inline: 'nearest'
      });
    }
  }

  // Check if element is in viewport
  isElementInViewport(elementId: string): boolean {
    const element = document.getElementById(elementId);
    if (!element) return false;

    const rect = element.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= window.innerHeight &&
      rect.right <= window.innerWidth
    );
  }

  // Scroll element into view if not visible
  scrollToElementIfNeeded(elementId: string) {
    if (!this.isElementInViewport(elementId)) {
      this.scrollToSection(elementId);
    }
  }
}

@Component({
  template: `
    <nav class="fixed-header">
      <button (click)="scrollToSection('section1')">Section 1</button>
      <button (click)="scrollToSection('section2')">Section 2</button>
      <button (click)="scrollToTop()">Top</button>
    </nav>

    <main>
      <section id="section1">
        <h2>Section 1</h2>
        <p>Content...</p>
      </section>

      <section id="section2">
        <h2>Section 2</h2>
        <p>Content...</p>
      </section>
    </main>

    <div class="scroll-info">
      Position: {{ scrollPosition[0] }}, {{ scrollPosition[1] }}
    </div>
  `
})
export class ScrollablePageComponent implements OnInit, OnDestroy {
  scrollPosition: [number, number] = [0, 0];
  private scrollSubscription?: Subscription;

  constructor(private scrollService: ScrollService) {}

  ngOnInit() {
    // Set offset for fixed header
    this.scrollService.setDynamicOffset();
    
    // Configure scroll restoration
    this.scrollService.configureScrollRestoration('manual');

    // Monitor scroll position
    this.scrollSubscription = interval(100).subscribe(() => {
      this.scrollPosition = this.scrollService.getCurrentScrollPosition();
    });
  }

  ngOnDestroy() {
    this.scrollSubscription?.unsubscribe();
  }

  scrollToSection(sectionId: string) {
    this.scrollService.scrollToSection(sectionId);
  }

  scrollToTop() {
    this.scrollService.scrollToTop();
  }

  scrollToCoordinates() {
    this.scrollService.scrollToCoordinates(0, 500);
  }
}

// Router integration with scroll management
@Injectable()
export class RouterScrollService {
  constructor(
    private router: Router,
    private viewportScroller: ViewportScroller
  ) {
    this.setupScrollBehavior();
  }

  private setupScrollBehavior() {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe((event: NavigationEnd) => {
      // Handle fragment-based navigation
      if (event.urlAfterRedirects.includes('#')) {
        const fragment = event.urlAfterRedirects.split('#')[1];
        setTimeout(() => {
          this.viewportScroller.scrollToAnchor(fragment);
        }, 100);
      } else {
        // Scroll to top for regular navigation
        this.viewportScroller.scrollToPosition([0, 0]);
      }
    });
  }
}

DOM Access

DOCUMENT Token - Document Injection

Injection token for accessing the Document object.

/**
 * Injection token for the Document object
 * Provides safe access to document in universal applications
 */
export const DOCUMENT: InjectionToken<Document>;

Usage Examples:

@Injectable()
export class DomService {
  constructor(@Inject(DOCUMENT) private document: Document) {}

  // Safe document access
  createElement(tagName: string): HTMLElement {
    return this.document.createElement(tagName);
  }

  // Query DOM elements
  getElementById(id: string): HTMLElement | null {
    return this.document.getElementById(id);
  }

  querySelector(selector: string): Element | null {
    return this.document.querySelector(selector);
  }

  querySelectorAll(selector: string): NodeListOf<Element> {
    return this.document.querySelectorAll(selector);
  }

  // Document properties
  getTitle(): string {
    return this.document.title;
  }

  setTitle(title: string): void {
    this.document.title = title;
  }

  getUrl(): string {
    return this.document.URL;
  }

  // Cookie operations
  getCookie(name: string): string | null {
    const value = `; ${this.document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) {
      return parts.pop()?.split(';').shift() || null;
    }
    return null;
  }

  setCookie(name: string, value: string, days?: number): void {
    let expires = '';
    if (days) {
      const date = new Date();
      date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
      expires = `; expires=${date.toUTCString()}`;
    }
    this.document.cookie = `${name}=${value}${expires}; path=/`;
  }

  // Style manipulation
  addStylesheet(href: string): void {
    const link = this.document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;
    this.document.head.appendChild(link);
  }

  // Meta tag operations
  setMetaTag(name: string, content: string): void {
    let meta = this.document.querySelector(`meta[name="${name}"]`) as HTMLMetaElement;
    if (!meta) {
      meta = this.document.createElement('meta') as HTMLMetaElement;
      meta.name = name;
      this.document.head.appendChild(meta);
    }
    meta.content = content;
  }

  getMetaTag(name: string): string | null {
    const meta = this.document.querySelector(`meta[name="${name}"]`) as HTMLMetaElement;
    return meta ? meta.content : null;
  }
}

@Component({})
export class DomManipulationComponent {
  constructor(
    @Inject(DOCUMENT) private document: Document,
    private domService: DomService
  ) {}

  ngOnInit() {
    // Set page title
    this.domService.setTitle('My Angular App');

    // Set meta tags for SEO
    this.domService.setMetaTag('description', 'Angular application with DOM utilities');
    this.domService.setMetaTag('keywords', 'Angular, TypeScript, DOM');

    // Add custom stylesheet
    this.domService.addStylesheet('/assets/custom.css');
  }

  manipulateDOM() {
    // Create and append element
    const div = this.domService.createElement('div');
    div.textContent = 'Dynamically created element';
    div.className = 'dynamic-content';
    
    const container = this.domService.getElementById('dynamic-container');
    if (container) {
      container.appendChild(div);
    }
  }

  manageCookies() {
    // Set cookie
    this.domService.setCookie('user-preference', 'dark-theme', 30);

    // Get cookie
    const preference = this.domService.getCookie('user-preference');
    console.log('User preference:', preference);
  }

  queryDOM() {
    // Query multiple elements
    const buttons = this.domService.querySelectorAll('button');
    buttons.forEach((button, index) => {
      button.addEventListener('click', () => {
        console.log(`Button ${index} clicked`);
      });
    });
  }
}

// Universal document service
@Injectable()
export class UniversalDocumentService {
  constructor(
    @Inject(DOCUMENT) private document: Document,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  // Safe document operations that work in both browser and server
  safeGetElementById(id: string): HTMLElement | null {
    if (isPlatformBrowser(this.platformId)) {
      return this.document.getElementById(id);
    }
    return null;
  }

  safeAddEventListener(elementId: string, event: string, handler: EventListener): void {
    if (isPlatformBrowser(this.platformId)) {
      const element = this.document.getElementById(elementId);
      if (element) {
        element.addEventListener(event, handler);
      }
    }
  }

  // Safe cookie operations
  safeCookieAccess(): boolean {
    return isPlatformBrowser(this.platformId) && !!this.document.cookie;
  }

  getClientInfo(): any {
    if (isPlatformBrowser(this.platformId)) {
      return {
        userAgent: navigator.userAgent,
        url: this.document.URL,
        title: this.document.title,
        referrer: this.document.referrer
      };
    }
    return {
      userAgent: 'Server',
      url: 'Unknown',
      title: 'Server Rendered',
      referrer: ''
    };
  }
}

Utility Functions and Services

Cookie Utilities

Functions for parsing and handling cookies.

/**
 * Parse cookie value from cookie string
 * Internal utility function (private export)
 */
export function ɵparseCookieValue(cookieStr: string, name: string): string | null;

XHR Factory

Factory for creating XMLHttpRequest instances.

/**
 * Abstract factory for creating XMLHttpRequest objects
 * Allows customization of XHR creation for testing or special needs
 */
export abstract class XhrFactory {
  abstract build(): XMLHttpRequest;
}

Usage Examples:

// Custom XHR factory for testing
@Injectable()
export class MockXhrFactory extends XhrFactory {
  build(): XMLHttpRequest {
    // Return mock XMLHttpRequest for testing
    return new MockXMLHttpRequest() as any;
  }
}

// Custom XHR factory with additional configuration
@Injectable()
export class ConfiguredXhrFactory extends XhrFactory {
  build(): XMLHttpRequest {
    const xhr = new XMLHttpRequest();
    
    // Add custom configuration
    xhr.timeout = 30000; // 30 second timeout
    
    // Add common headers
    xhr.addEventListener('loadstart', () => {
      xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    });

    return xhr;
  }
}

// Cookie parsing utility
@Injectable()
export class CookieService {
  constructor(@Inject(DOCUMENT) private document: Document) {}

  // Parse cookie using Angular's internal utility
  private parseCookieValue(name: string): string | null {
    // Note: ɵparseCookieValue is internal, use at your own risk
    return (ɵparseCookieValue as any)(this.document.cookie, name);
  }

  // Safe cookie parsing
  getCookie(name: string): string | null {
    try {
      const value = `; ${this.document.cookie}`;
      const parts = value.split(`; ${name}=`);
      if (parts.length === 2) {
        return decodeURIComponent(parts.pop()?.split(';').shift() || '');
      }
    } catch (error) {
      console.error('Error parsing cookie:', error);
    }
    return null;
  }

  setCookie(name: string, value: string, options: CookieOptions = {}): void {
    let cookieString = `${name}=${encodeURIComponent(value)}`;

    if (options.expires) {
      cookieString += `; expires=${options.expires.toUTCString()}`;
    }

    if (options.path) {
      cookieString += `; path=${options.path}`;
    }

    if (options.domain) {
      cookieString += `; domain=${options.domain}`;
    }

    if (options.secure) {
      cookieString += '; secure';
    }

    if (options.sameSite) {
      cookieString += `; samesite=${options.sameSite}`;
    }

    this.document.cookie = cookieString;
  }

  deleteCookie(name: string, path?: string): void {
    this.setCookie(name, '', {
      expires: new Date('1970-01-01'),
      path: path
    });
  }

  getAllCookies(): { [key: string]: string } {
    const cookies: { [key: string]: string } = {};
    
    if (this.document.cookie) {
      this.document.cookie.split(';').forEach(cookie => {
        const [name, value] = cookie.trim().split('=');
        if (name && value) {
          cookies[name] = decodeURIComponent(value);
        }
      });
    }

    return cookies;
  }
}

interface CookieOptions {
  expires?: Date;
  path?: string;
  domain?: string;
  secure?: boolean;
  sameSite?: 'strict' | 'lax' | 'none';
}

// Platform-aware utility service
@Injectable()
export class PlatformUtilityService {
  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Inject(DOCUMENT) private document: Document
  ) {}

  // Environment checks
  isBrowser(): boolean {
    return isPlatformBrowser(this.platformId);
  }

  isServer(): boolean {
    return isPlatformServer(this.platformId);
  }

  isWebWorker(): boolean {
    return isPlatformWorkerApp(this.platformId) || isPlatformWorkerUi(this.platformId);
  }

  // Feature detection
  hasLocalStorage(): boolean {
    if (!this.isBrowser()) return false;
    
    try {
      const test = 'test';
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
      return true;
    } catch {
      return false;
    }
  }

  hasSessionStorage(): boolean {
    if (!this.isBrowser()) return false;
    
    try {
      const test = 'test';
      sessionStorage.setItem(test, test);
      sessionStorage.removeItem(test);
      return true;
    } catch {
      return false;
    }
  }

  hasCookieSupport(): boolean {
    if (!this.isBrowser()) return false;
    
    try {
      this.document.cookie = 'test=test';
      const hasCookie = this.document.cookie.indexOf('test=test') !== -1;
      this.document.cookie = 'test=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
      return hasCookie;
    } catch {
      return false;
    }
  }

  // Utility methods
  getEnvironmentInfo(): any {
    return {
      platform: {
        browser: this.isBrowser(),
        server: this.isServer(),
        webWorker: this.isWebWorker()
      },
      features: {
        localStorage: this.hasLocalStorage(),
        sessionStorage: this.hasSessionStorage(),
        cookies: this.hasCookieSupport()
      },
      userAgent: this.isBrowser() ? navigator.userAgent : 'Server'
    };
  }
}

Types and Constants

// Platform ID constants (private exports)
export const ɵPLATFORM_BROWSER_ID: string;
export const ɵPLATFORM_SERVER_ID: string;
export const ɵPLATFORM_WORKER_APP_ID: string;
export const ɵPLATFORM_WORKER_UI_ID: string;

// Injection tokens
export const DOCUMENT: InjectionToken<Document>;

// Version information
export const VERSION: Version;