improve: performance
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<div class="app-content" [attr.aria-hidden]="isLanguageSelectorOpen() ? 'true' : null" [attr.inert]="isLanguageSelectorOpen() ? '' : null">
|
<div class="app-content" [attr.aria-hidden]="isLanguageSelectorOpen() ? 'true' : null" [attr.inert]="isLanguageSelectorOpen() ? '' : null">
|
||||||
<router-outlet (activate)="scrollToTopAfterOutletActivation()"></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
+10
-55
@@ -1,22 +1,18 @@
|
|||||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
import { DOCUMENT } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
DestroyRef,
|
|
||||||
computed,
|
computed,
|
||||||
effect,
|
effect,
|
||||||
inject,
|
inject,
|
||||||
PLATFORM_ID,
|
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
||||||
import type { OnDestroy, OnInit } from '@angular/core';
|
import type { OnDestroy, OnInit } from '@angular/core';
|
||||||
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { filter } from 'rxjs';
|
|
||||||
|
|
||||||
import type { LanguageCode } from '../languages/language.types';
|
import type { LanguageCode } from '../languages/language.types';
|
||||||
import type { LanguageOption } from './app.types';
|
import type { LanguageOption } from './app.types';
|
||||||
import { LanguageService } from './services/language.service';
|
import { ShellLanguageService } from './services/shell-language.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -27,14 +23,11 @@ import { LanguageService } from './services/language.service';
|
|||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, OnDestroy {
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
private readonly document = inject(DOCUMENT);
|
private readonly document = inject(DOCUMENT);
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly shellLanguageService = inject(ShellLanguageService);
|
||||||
private readonly languageService = inject(LanguageService);
|
|
||||||
private readonly platformId = inject(PLATFORM_ID);
|
|
||||||
private readonly router = inject(Router);
|
|
||||||
|
|
||||||
protected readonly content = this.languageService.shellContent;
|
protected readonly content = this.shellLanguageService.content;
|
||||||
protected readonly currentLanguageCode = this.languageService.languageCode;
|
protected readonly currentLanguageCode = this.shellLanguageService.languageCode;
|
||||||
protected readonly isLanguageConfirmed = this.languageService.isLanguageConfirmed;
|
protected readonly isLanguageConfirmed = this.shellLanguageService.isLanguageConfirmed;
|
||||||
protected readonly isLanguageSelectorOpen = signal(false);
|
protected readonly isLanguageSelectorOpen = signal(false);
|
||||||
protected readonly selectedLanguage = signal<LanguageCode>('en');
|
protected readonly selectedLanguage = signal<LanguageCode>('en');
|
||||||
protected readonly languageOptions: readonly LanguageOption[] = [
|
protected readonly languageOptions: readonly LanguageOption[] = [
|
||||||
@@ -42,7 +35,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
{ code: 'de', accent: 'DE' },
|
{ code: 'de', accent: 'DE' },
|
||||||
];
|
];
|
||||||
protected readonly dialogContent = computed(() =>
|
protected readonly dialogContent = computed(() =>
|
||||||
this.languageService.getShellPack(this.selectedLanguage()),
|
this.shellLanguageService.getPack(this.selectedLanguage()),
|
||||||
);
|
);
|
||||||
protected readonly languageSwitcherLabel = computed(() =>
|
protected readonly languageSwitcherLabel = computed(() =>
|
||||||
this.getLanguageLabel(this.currentLanguageCode(), this.content()),
|
this.getLanguageLabel(this.currentLanguageCode(), this.content()),
|
||||||
@@ -54,17 +47,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
this.document.body.style.overflow = overflowValue;
|
this.document.body.style.overflow = overflowValue;
|
||||||
this.document.documentElement.style.overflow = overflowValue;
|
this.document.documentElement.style.overflow = overflowValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.router.events
|
|
||||||
.pipe(
|
|
||||||
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
|
|
||||||
takeUntilDestroyed(this.destroyRef),
|
|
||||||
)
|
|
||||||
.subscribe((event: NavigationEnd): void => this.scrollToTopAfterRouteChange(event));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const initialLanguage = this.languageService.initializeFromStorage();
|
const initialLanguage = this.shellLanguageService.initializeFromStorage();
|
||||||
this.selectedLanguage.set(initialLanguage);
|
this.selectedLanguage.set(initialLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +60,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected confirmLanguage(): void {
|
protected confirmLanguage(): void {
|
||||||
this.languageService.confirmLanguage(this.selectedLanguage());
|
this.shellLanguageService.confirmLanguage(this.selectedLanguage());
|
||||||
this.isLanguageSelectorOpen.set(false);
|
this.isLanguageSelectorOpen.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,37 +84,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
return language === 'de' ? content.app.languageGerman : content.app.languageEnglish;
|
return language === 'de' ? content.app.languageGerman : content.app.languageEnglish;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected scrollToTopAfterOutletActivation(): void {
|
|
||||||
if (!isPlatformBrowser(this.platformId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollDocumentToTop();
|
|
||||||
this.document.defaultView?.requestAnimationFrame((): void => this.scrollDocumentToTop());
|
|
||||||
this.document.defaultView?.setTimeout((): void => this.scrollDocumentToTop(), 0);
|
|
||||||
this.document.defaultView?.setTimeout((): void => this.scrollDocumentToTop(), 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
private scrollToTopAfterRouteChange(event: NavigationEnd): void {
|
|
||||||
if (!isPlatformBrowser(this.platformId) || event.urlAfterRedirects.includes('#')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollToTopAfterOutletActivation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private scrollDocumentToTop(): void {
|
|
||||||
const scrollingElement = this.document.scrollingElement ?? this.document.documentElement;
|
|
||||||
|
|
||||||
scrollingElement.scrollTop = 0;
|
|
||||||
scrollingElement.scrollLeft = 0;
|
|
||||||
this.document.documentElement.scrollTop = 0;
|
|
||||||
this.document.documentElement.scrollLeft = 0;
|
|
||||||
this.document.body.scrollTop = 0;
|
|
||||||
this.document.body.scrollLeft = 0;
|
|
||||||
this.document.defaultView?.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.document.body.style.overflow = '';
|
this.document.body.style.overflow = '';
|
||||||
this.document.documentElement.style.overflow = '';
|
this.document.documentElement.style.overflow = '';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { global } from '../../global';
|
import { global } from '../../global';
|
||||||
import { LanguageService } from '../services/language.service';
|
import { ShellLanguageService } from '../services/shell-language.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-footer',
|
selector: 'app-footer',
|
||||||
@@ -11,9 +11,9 @@ import { LanguageService } from '../services/language.service';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FooterComponent {
|
export class FooterComponent {
|
||||||
private readonly languageService = inject(LanguageService);
|
private readonly shellLanguageService = inject(ShellLanguageService);
|
||||||
|
|
||||||
protected readonly content = this.languageService.shellContent;
|
protected readonly content = this.shellLanguageService.content;
|
||||||
protected readonly global = global;
|
protected readonly global = global;
|
||||||
protected readonly currentYear = new Date().getFullYear();
|
protected readonly currentYear = new Date().getFullYear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable, signal } from '@angular/core';
|
||||||
|
|
||||||
|
import type { LanguageCode } from '../../languages/language.types';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class LanguagePreferenceService {
|
||||||
|
private static readonly storageKey = 'portfolio-language';
|
||||||
|
private readonly currentLanguageCode = signal<LanguageCode>('en');
|
||||||
|
private readonly languageConfirmed = signal(true);
|
||||||
|
|
||||||
|
readonly languageCode = this.currentLanguageCode.asReadonly();
|
||||||
|
readonly isLanguageConfirmed = this.languageConfirmed.asReadonly();
|
||||||
|
|
||||||
|
initializeFromStorage(): LanguageCode {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return this.currentLanguageCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedLanguage = window.localStorage.getItem(LanguagePreferenceService.storageKey);
|
||||||
|
if (storedLanguage === 'de' || storedLanguage === 'en') {
|
||||||
|
this.currentLanguageCode.set(storedLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.currentLanguageCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmLanguage(code: LanguageCode): void {
|
||||||
|
this.currentLanguageCode.set(code);
|
||||||
|
this.languageConfirmed.set(true);
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.localStorage.setItem(LanguagePreferenceService.storageKey, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +1,27 @@
|
|||||||
import { Injectable, computed, signal } from '@angular/core';
|
import { Injectable, computed, inject } from '@angular/core';
|
||||||
|
|
||||||
import { deLanguage } from '../../languages/de';
|
import { deLanguage } from '../../languages/de';
|
||||||
import { enLanguage } from '../../languages/en';
|
import { enLanguage } from '../../languages/en';
|
||||||
import type { LanguageCode, LanguagePack, LanguageShellPack } from '../../languages/language.types';
|
import type { LanguageCode, LanguagePack } from '../../languages/language.types';
|
||||||
|
import { LanguagePreferenceService } from './language-preference.service';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class LanguageService {
|
export class LanguageService {
|
||||||
private static readonly storageKey = 'portfolio-language';
|
private readonly languagePreferenceService = inject(LanguagePreferenceService);
|
||||||
private readonly currentLanguageCode = signal<LanguageCode>('en');
|
|
||||||
private readonly languageConfirmed = signal(true);
|
|
||||||
|
|
||||||
readonly languageCode = this.currentLanguageCode.asReadonly();
|
readonly languageCode = this.languagePreferenceService.languageCode;
|
||||||
readonly isLanguageConfirmed = this.languageConfirmed.asReadonly();
|
readonly isLanguageConfirmed = this.languagePreferenceService.isLanguageConfirmed;
|
||||||
readonly content = computed<LanguagePack>(() => this.getPack(this.currentLanguageCode()));
|
readonly content = computed<LanguagePack>(() => this.getPack(this.languageCode()));
|
||||||
readonly shellContent = computed<LanguageShellPack>(() => this.toShellPack(this.content()));
|
|
||||||
|
|
||||||
initializeFromStorage(): LanguageCode {
|
initializeFromStorage(): LanguageCode {
|
||||||
if (typeof window === 'undefined') {
|
return this.languagePreferenceService.initializeFromStorage();
|
||||||
return this.currentLanguageCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedLanguage = window.localStorage.getItem(LanguageService.storageKey);
|
|
||||||
if (storedLanguage === 'de' || storedLanguage === 'en') {
|
|
||||||
this.currentLanguageCode.set(storedLanguage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.currentLanguageCode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmLanguage(code: LanguageCode): void {
|
confirmLanguage(code: LanguageCode): void {
|
||||||
this.currentLanguageCode.set(code);
|
this.languagePreferenceService.confirmLanguage(code);
|
||||||
this.languageConfirmed.set(true);
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.localStorage.setItem(LanguageService.storageKey, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getShellPack(code: LanguageCode): LanguageShellPack {
|
|
||||||
return this.toShellPack(this.getPack(code));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPack(code: LanguageCode): LanguagePack {
|
getPack(code: LanguageCode): LanguagePack {
|
||||||
return code === 'de' ? deLanguage : enLanguage;
|
return code === 'de' ? deLanguage : enLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private toShellPack(pack: LanguagePack): LanguageShellPack {
|
|
||||||
return {
|
|
||||||
app: pack.app,
|
|
||||||
footer: pack.footer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable, computed, inject } from '@angular/core';
|
||||||
|
|
||||||
|
import { deShellLanguage, enShellLanguage } from '../../languages/shell';
|
||||||
|
import type { LanguageCode, LanguageShellPack } from '../../languages/language.types';
|
||||||
|
import { LanguagePreferenceService } from './language-preference.service';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class ShellLanguageService {
|
||||||
|
private readonly languagePreferenceService = inject(LanguagePreferenceService);
|
||||||
|
|
||||||
|
readonly languageCode = this.languagePreferenceService.languageCode;
|
||||||
|
readonly isLanguageConfirmed = this.languagePreferenceService.isLanguageConfirmed;
|
||||||
|
readonly content = computed<LanguageShellPack>(() => this.getPack(this.languageCode()));
|
||||||
|
|
||||||
|
initializeFromStorage(): LanguageCode {
|
||||||
|
return this.languagePreferenceService.initializeFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmLanguage(code: LanguageCode): void {
|
||||||
|
this.languagePreferenceService.confirmLanguage(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPack(code: LanguageCode): LanguageShellPack {
|
||||||
|
return code === 'de' ? deShellLanguage : enShellLanguage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import type { LanguageShellPack } from './language.types';
|
||||||
|
|
||||||
|
export const enShellLanguage: LanguageShellPack = {
|
||||||
|
app: {
|
||||||
|
selectorTitle: 'Select language',
|
||||||
|
changeLanguage: 'Language',
|
||||||
|
changeLanguageAriaLabel: 'Change language',
|
||||||
|
closeSelector: 'Close language selection',
|
||||||
|
languageEnglish: 'English',
|
||||||
|
languageGerman: 'German',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
noscriptMessage: 'Please enable JavaScript for the full experience.',
|
||||||
|
imprintLink: 'Imprint',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deShellLanguage: LanguageShellPack = {
|
||||||
|
app: {
|
||||||
|
selectorTitle: 'Sprache auswählen',
|
||||||
|
changeLanguage: 'Sprache',
|
||||||
|
changeLanguageAriaLabel: 'Sprache ändern',
|
||||||
|
closeSelector: 'Sprachauswahl schließen',
|
||||||
|
languageEnglish: 'Englisch',
|
||||||
|
languageGerman: 'Deutsch',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
noscriptMessage: 'Bitte aktiviere JavaScript um alles zu sehen.',
|
||||||
|
imprintLink: 'Impressum',
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user