diff --git a/src/app/components/hero-section/hero-section.component.html b/src/app/components/hero-section/hero-section.component.html index 2ed787f..711a8a1 100644 --- a/src/app/components/hero-section/hero-section.component.html +++ b/src/app/components/hero-section/hero-section.component.html @@ -1,14 +1,14 @@ -@if (section.image) { -
-

{{section.headline}}

+@if (section().image) { +
+

{{section().headline}}

-
{{section.subhead}}
- - {{section.call_to_action}} +
{{section().subhead}}
+
+ {{section().call_to_action}}
-
{{section.image_info}}
+
{{section().image_info}}
} @else { -

{{section.headline}}

+

{{section().headline}}

} \ No newline at end of file diff --git a/src/app/components/hero-section/hero-section.component.ts b/src/app/components/hero-section/hero-section.component.ts index 0443110..fd2ae52 100644 --- a/src/app/components/hero-section/hero-section.component.ts +++ b/src/app/components/hero-section/hero-section.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { HeroSection } from '../../models/page.model'; @Component({ @@ -9,6 +9,5 @@ import { HeroSection } from '../../models/page.model'; styleUrl: './hero-section.component.scss' }) export class HeroSectionComponent { - @Input('hero-section') - section = {} as HeroSection; + section = input({} as HeroSection, { alias: 'hero-section' }); } diff --git a/src/app/components/image-slider/image-slider.component.html b/src/app/components/image-slider/image-slider.component.html index 7799897..245fec4 100644 --- a/src/app/components/image-slider/image-slider.component.html +++ b/src/app/components/image-slider/image-slider.component.html @@ -5,7 +5,7 @@
{{img.title}}
- @if(images.length > 1) { + @if(images().length > 1) { diff --git a/src/app/components/image-slider/image-slider.component.ts b/src/app/components/image-slider/image-slider.component.ts index adf3acb..9851c15 100644 --- a/src/app/components/image-slider/image-slider.component.ts +++ b/src/app/components/image-slider/image-slider.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, ElementRef, Input, ViewChild, inject } from '@angular/core'; +import { Component, ElementRef, inject, input, viewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import KeenSlider, { KeenSliderInstance } from 'keen-slider'; @@ -16,21 +16,21 @@ import { MediaStorageService } from '../../services/common/media-storage.service export class ImageSliderComponent { private readonly _storageService = inject(MediaStorageService); - @Input() images = [] as SliderImage[]; - @Input() height = '300px'; - @ViewChild('sliderRef') sliderRef!: ElementRef; + images = input([] as SliderImage[]); + height = input('300px'); + sliderRef = viewChild>('sliderRef'); slider!: KeenSliderInstance; images$!: Promise[]>; ngOnInit() { - this.images$ = Promise.all(this.images.map((img) => this.resolveUrl(img))).finally(() => + this.images$ = Promise.all(this.images().map((img) => this.resolveUrl(img))).finally(() => setTimeout(this.slider.update, 0) ); } ngAfterViewInit() { - this.slider = new KeenSlider(this.sliderRef.nativeElement, { + this.slider = new KeenSlider(this.sliderRef()!.nativeElement, { mode: 'free-snap', loop: true, slides: { diff --git a/src/app/components/input-section/input-section.component.html b/src/app/components/input-section/input-section.component.html index 14064e9..265cef2 100644 --- a/src/app/components/input-section/input-section.component.html +++ b/src/app/components/input-section/input-section.component.html @@ -1,46 +1,46 @@ - + - {{item.value.caption}} + {{definition.value.caption}} - @if(item.type === 'select') { + @if(definition.type === 'select') { - @for(opt of item.value.options; track $index) { + @for(opt of definition.value.options; track $index) { {{opt}} } - } @else if (item.type === 'textarea') { + } @else if (definition.type === 'textarea') { {{validationMessage}} - } @else if (item.type === 'text') { + } @else if (definition.type === 'text') { {{validationMessage}} diff --git a/src/app/components/input-section/input-section.component.ts b/src/app/components/input-section/input-section.component.ts index 68f28cb..9f17c67 100644 --- a/src/app/components/input-section/input-section.component.ts +++ b/src/app/components/input-section/input-section.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core'; +import { Component, forwardRef, input, model, output } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; @@ -31,28 +31,30 @@ import { InputDefinition, InputValue } from '../../models/content.model'; ] }) export class InputSectionComponent implements ControlValueAccessor { - @Input({ required: true }) item!: InputDefinition; - @Input() disabled = false; - @Input('ngModel') value!: InputValue; - @Output('ngModelChange') change = new EventEmitter(); + item = input.required(); + disabled = model(false); + value = model(undefined, { alias: 'ngModel' }); + change = output({ alias: 'ngModelChange' }); get valid(): boolean { - if ('validation' in this.item.value && this.item.value.validation) { - const pattern = new RegExp(this.item.value.validation); - return pattern.test(`${this.value}`); + const definition = this.item(); + if ('validation' in definition.value && definition.value.validation) { + const pattern = new RegExp(definition.value.validation); + return pattern.test(`${this.value()}`); } return true; } get validationMessage(): string { - if ('message' in this.item.value && this.item.value.message) { - return this.item.value.message; + const definition = this.item(); + if ('message' in definition.value && definition.value.message) { + return definition.value.message; } return 'Eingabe ungültig'; } writeValue(value: InputValue): void { - this.value = value; + this.value.set(value); } registerOnChange(fn: (v: InputValue) => void): void { @@ -64,11 +66,11 @@ export class InputSectionComponent implements ControlValueAccessor { } setDisabledState?(isDisabled: boolean): void { - this.disabled = isDisabled; + this.disabled.set(isDisabled); } updateValue(value: InputValue): void { - this.value = value; + this.value.set(value); this.change.emit(value); } } diff --git a/src/app/components/input-stepper/input-stepper.component.html b/src/app/components/input-stepper/input-stepper.component.html index e96117a..aa50e7a 100644 --- a/src/app/components/input-stepper/input-stepper.component.html +++ b/src/app/components/input-stepper/input-stepper.component.html @@ -1,8 +1,8 @@
- @for (item of definitions; track $index) { + @for (item of definitions(); track $index) {
+ [ngModel]="model(item)" (ngModelChange)="update($event, item.value.id)" />
} diff --git a/src/app/components/input-stepper/input-stepper.component.spec.ts b/src/app/components/input-stepper/input-stepper.component.spec.ts index 3106c5f..fe2eea3 100644 --- a/src/app/components/input-stepper/input-stepper.component.spec.ts +++ b/src/app/components/input-stepper/input-stepper.component.spec.ts @@ -15,8 +15,8 @@ describe('InputStepperComponent', () => { fixture = TestBed.createComponent(InputStepperComponent); component = fixture.componentInstance; - component.data = {}; - component.definitions = [ + fixture.componentRef.setInput('pageId', '0-test'); + fixture.componentRef.setInput('definitions', [ { type: 'textarea', value: { @@ -36,7 +36,8 @@ describe('InputStepperComponent', () => { multiline: true } } - ]; + ]); + fixture.componentRef.setInput('data', {}); fixture.detectChanges(); }); diff --git a/src/app/components/input-stepper/input-stepper.component.ts b/src/app/components/input-stepper/input-stepper.component.ts index 96cb1e1..726d246 100644 --- a/src/app/components/input-stepper/input-stepper.component.ts +++ b/src/app/components/input-stepper/input-stepper.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, QueryList, ViewChildren } from '@angular/core'; +import { AfterViewInit, Component, input, output, model, viewChildren } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; @@ -18,36 +18,37 @@ export interface ContinueEventArgs { selector: 'app-input-stepper', standalone: true, imports: [ - CommonModule, + CommonModule, FormsModule, - MatButtonModule, + MatButtonModule, MatIconModule, InputSectionComponent, - ProgressSpinnerComponent + ProgressSpinnerComponent, ], templateUrl: './input-stepper.component.html', styleUrl: './input-stepper.component.scss', animations: [expandTrigger('next')], }) -export class InputStepperComponent { - @Input({ required: true }) definitions!: InputDefinition[]; - @Input() data: UserDataItems = {}; - @Output() continue = new EventEmitter(); - @ViewChildren(InputSectionComponent) inputs!: QueryList; +export class InputStepperComponent implements AfterViewInit { + readonly definitions = input.required(); + readonly data = model>({}); + readonly continue = output(); + inputs = viewChildren(InputSectionComponent); done = false; disabled = true; private _step = 0; get last(): boolean { - return this._step >= this.definitions.length - 1; + return this._step >= this.definitions().length - 1; } get progress(): number { - return ((this._step + 1) / this.definitions.length) * 100; + return ((this._step + 1) / this.definitions().length) * 100; } ngAfterViewInit() { + // bypass ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. setTimeout(() => this.setDisabled(), 0); } @@ -59,24 +60,28 @@ export class InputStepperComponent { return index === this._step; } + model(definition: InputDefinition) { + return this.data()[definition.value.id]; + } + update(value: InputValue, id: string) { - this.data[id] = value; + this.data()[id] = value; this.setDisabled(); } next(): void { - const currentId = this.definitions[this._step].value.id; - if (!this.data[currentId]) { + const currentId = this.definitions()[this._step].value.id; + if (!this.data()[currentId]) { // no data entered hence no update fired - this.data[currentId] = undefined; + this.data()[currentId] = undefined; } this._step++; + this.setDone(); this.setDisabled(); - this.done = this._step === this.definitions.length; this.continue.emit({ completed: this.done, - data: { ...this.data } + data: { ...this.data() }, }); } @@ -84,8 +89,12 @@ export class InputStepperComponent { this.disabled = !this.isValid(); } + private setDone() { + this.done = this._step === this.definitions().length; + } + private isValid(): boolean { - const currentInput = this.inputs?.get(this._step); + const currentInput = this.inputs()?.at(this._step); return currentInput ? currentInput.valid : true; } } diff --git a/src/app/components/nav/nav.component.ts b/src/app/components/nav/nav.component.ts index 777126e..fb7868b 100644 --- a/src/app/components/nav/nav.component.ts +++ b/src/app/components/nav/nav.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ViewChild, inject } from '@angular/core'; +import { AfterViewInit, Component, inject, viewChild } from '@angular/core'; import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { AsyncPipe, CommonModule } from '@angular/common'; import { RouterLink, RouterLinkActive } from '@angular/router'; @@ -36,8 +36,7 @@ export class NavComponent implements AfterViewInit { private readonly _guideService = inject(GuideService); private readonly _breakpointObserver = inject(BreakpointObserver); - @ViewChild(MatSidenavContainer) - private _sidenavContainer!: MatSidenavContainer; + private _sidenavContainer = viewChild(MatSidenavContainer); private _routes: NavigationItem[] = []; currentGuide = {} as Guide; @@ -71,7 +70,7 @@ export class NavComponent implements AfterViewInit { return `${top}px`; }; - this.toolbarTop$ = this._sidenavContainer.scrollable.elementScrolled().pipe(map(onScroll)); + this.toolbarTop$ = this._sidenavContainer()!.scrollable.elementScrolled().pipe(map(onScroll)); // this.changeDetectorRef.detectChanges(); this.toolbarTop$.subscribe(); diff --git a/src/app/components/ui/expand/expand.component.html b/src/app/components/ui/expand/expand.component.html index 195e532..1e0a5b9 100644 --- a/src/app/components/ui/expand/expand.component.html +++ b/src/app/components/ui/expand/expand.component.html @@ -1,10 +1,10 @@ - {{title}} + {{title()}} - - {{description}} + + {{description()}}
diff --git a/src/app/components/ui/expand/expand.component.ts b/src/app/components/ui/expand/expand.component.ts index 3637f37..9f93505 100644 --- a/src/app/components/ui/expand/expand.component.ts +++ b/src/app/components/ui/expand/expand.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { MatAccordion, MatExpansionModule } from '@angular/material/expansion'; @Component({ @@ -10,6 +10,6 @@ import { MatAccordion, MatExpansionModule } from '@angular/material/expansion'; styleUrl: './expand.component.scss' }) export class ExpandComponent { - @Input({ required: true }) title!: string; - @Input() description = ''; + title = input.required(); + description = input(''); } diff --git a/src/app/components/ui/iframe/iframe.component.html b/src/app/components/ui/iframe/iframe.component.html index a9ee623..7080281 100644 --- a/src/app/components/ui/iframe/iframe.component.html +++ b/src/app/components/ui/iframe/iframe.component.html @@ -1,3 +1,3 @@ - \ No newline at end of file diff --git a/src/app/components/ui/iframe/iframe.component.ts b/src/app/components/ui/iframe/iframe.component.ts index f57d616..afed9a4 100644 --- a/src/app/components/ui/iframe/iframe.component.ts +++ b/src/app/components/ui/iframe/iframe.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { SafeUrlPipe } from '../../../pipes/safe-url.pipe'; import { IFrameContent } from '../../../models/content.model'; @@ -10,12 +10,13 @@ import { IFrameContent } from '../../../models/content.model'; styleUrl: './iframe.component.scss', }) export class IFrameComponent { - @Input({ required: true }) value!: IFrameContent['value']; + value = input.required(); get src() { - if (this.value.type === 'youtube') { - return `https://www.youtube.com/embed/${this.value.src}`; + if (this.value().type === 'youtube') { + return `https://www.youtube.com/embed/${this.value().src}`; } - return this.value.src; + return this.value().src; } + } diff --git a/src/app/components/ui/loading/loading.component.html b/src/app/components/ui/loading/loading.component.html index a8c1129..7780bdb 100644 --- a/src/app/components/ui/loading/loading.component.html +++ b/src/app/components/ui/loading/loading.component.html @@ -1,4 +1,4 @@ -@if(loading) { +@if(loading()) {
Lade Inhalt...
diff --git a/src/app/components/ui/loading/loading.component.ts b/src/app/components/ui/loading/loading.component.ts index 5794889..944bcd5 100644 --- a/src/app/components/ui/loading/loading.component.ts +++ b/src/app/components/ui/loading/loading.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { MatProgressBarModule } from '@angular/material/progress-bar'; @Component({ @@ -8,5 +8,5 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; templateUrl: './loading.component.html', }) export class LoadingComponent { - @Input() loading = true; + loading = input(true); } diff --git a/src/app/components/ui/markdown/markdown.component.html b/src/app/components/ui/markdown/markdown.component.html index ab4bf55..22f24ad 100644 --- a/src/app/components/ui/markdown/markdown.component.html +++ b/src/app/components/ui/markdown/markdown.component.html @@ -1 +1 @@ - + diff --git a/src/app/components/ui/markdown/markdown.component.ts b/src/app/components/ui/markdown/markdown.component.ts index 234474e..6ea908b 100644 --- a/src/app/components/ui/markdown/markdown.component.ts +++ b/src/app/components/ui/markdown/markdown.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, inject } from '@angular/core'; +import { Component, inject, input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { MarkedPipe } from '../../../pipes/marked.pipe'; @@ -12,7 +12,7 @@ import { MarkedPipe } from '../../../pipes/marked.pipe'; }) export class MarkdownComponent { private _router = inject(Router); - @Input({ required: true }) content!: string; + content = input.required(); // https://stackoverflow.com/questions/51764517/use-angular-router-inside-markdown-links public onClick(e: MouseEvent): void { diff --git a/src/app/components/ui/progress-spinner/progress-spinner.component.html b/src/app/components/ui/progress-spinner/progress-spinner.component.html index d37f5b8..ca28b0f 100644 --- a/src/app/components/ui/progress-spinner/progress-spinner.component.html +++ b/src/app/components/ui/progress-spinner/progress-spinner.component.html @@ -1,6 +1,6 @@ -
- +
+
diff --git a/src/app/components/ui/progress-spinner/progress-spinner.component.ts b/src/app/components/ui/progress-spinner/progress-spinner.component.ts index f0621ac..80491d5 100644 --- a/src/app/components/ui/progress-spinner/progress-spinner.component.ts +++ b/src/app/components/ui/progress-spinner/progress-spinner.component.ts @@ -1,18 +1,18 @@ import { CommonModule } from '@angular/common'; -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @Component({ - selector: 'app-progress-spinner', - standalone: true, - imports: [CommonModule, MatIconModule, MatProgressSpinnerModule], - templateUrl: './progress-spinner.component.html', - styleUrl: './progress-spinner.component.scss' + selector: 'app-progress-spinner', + standalone: true, + imports: [CommonModule, MatIconModule, MatProgressSpinnerModule], + templateUrl: './progress-spinner.component.html', + styleUrl: './progress-spinner.component.scss', }) export class ProgressSpinnerComponent { - @Input() color = 'primary'; - @Input() size = 40; - @Input() value = 0; - @Input() disabled = false; + color = input('primary'); + size = input(40); + value = input(0); + disabled = input(false); }