import { Component, computed, effect, inject, OnInit, OnDestroy, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { RouterOutlet } from '@angular/router'; import { NavbarComponent } from './navbar/navbar.component'; import { MediaPlayerComponent } from './media-player/media-player.component'; import { FooterComponent } from './footer/footer.component'; import { StructuredDataComponent } from './structured-data/structured-data.component'; import { StationConfigService } from './services/station-config.service'; import { SeoService } from './services/seo.service'; import { loadAppConfigAsync } from './services/app-config.service'; @Component({ selector: 'app-root', standalone: true, imports: [RouterOutlet, NavbarComponent, MediaPlayerComponent, FooterComponent, StructuredDataComponent], templateUrl: './app.html', styleUrl: './app.scss', }) export class App implements OnInit, OnDestroy { private stationConfig = inject(StationConfigService); private seoService = inject(SeoService); private platformId = inject(PLATFORM_ID); private http = inject(HttpClient); readonly hasStream = computed(() => { const cfg = this.stationConfig.config(); return !!cfg.stream_url && cfg.stream_url.trim().length > 0; }); private navbarEl: HTMLElement | null = null; private resizeObserver: ResizeObserver | null = null; constructor() { // These DOM operations only run in the browser. if (!isPlatformBrowser(this.platformId)) return; // Update favicon when station config changes. effect(() => { const config = this.stationConfig.config(); const faviconEl = document.querySelector('link[rel="icon"]') as HTMLLinkElement | null; if (faviconEl && config.logo_url) { faviconEl.href = config.logo_url; } }); effect(() => { if (this.hasStream()) { document.body.classList.add('has-stream'); } else { document.body.classList.remove('has-stream'); } }); } ngOnInit(): void { // Load station config on BOTH server and browser. On the server this // fetches fresh data from the API so SSR renders with real colors, // assets, and content — not DEFAULT_CONFIG baked at build time. this.stationConfig.load().catch(() => {}); // These DOM operations only run in the browser. if (!isPlatformBrowser(this.platformId)) return; // Load runtime config.json asynchronously in the background — do not // block rendering. loadAppConfigAsync(this.http).catch(() => {}); // Start watching router for automatic SEO metadata updates per route. this.seoService.watchRouter(); // Handle the initial route (NavigationEnd already fired before ngOnInit). this.seoService.resetToDefaults(); // Defer until the navbar is rendered in the DOM. queueMicrotask(() => { this.navbarEl = document.querySelector('app-navbar'); if (!this.navbarEl) return; this.updateNavbarHeight(); this.resizeObserver = new ResizeObserver(() => this.updateNavbarHeight()); this.resizeObserver.observe(this.navbarEl!); }); } ngOnDestroy(): void { if (isPlatformBrowser(this.platformId)) { this.resizeObserver?.disconnect(); } } private updateNavbarHeight(): void { if (!this.navbarEl) return; const h = this.navbarEl.offsetHeight; document.documentElement.style.setProperty('--navbar-height', `${h}px`); } }