Skip to content

Commit

Permalink
migrated input, output, model to signals
Browse files Browse the repository at this point in the history
  • Loading branch information
ortwic committed May 13, 2024
1 parent 28e2100 commit 0815fab
Show file tree
Hide file tree
Showing 20 changed files with 121 additions and 110 deletions.
16 changes: 8 additions & 8 deletions src/app/components/hero-section/hero-section.component.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
@if (section.image) {
<div class="header" [style.backgroundImage]="'url(' + section.image + ')'">
<h1>{{section.headline}}</h1>
@if (section().image) {
<div class="header" [style.backgroundImage]="'url(' + section().image + ')'">
<h1>{{section().headline}}</h1>
<section>
<div class="subhead">{{section.subhead}}</div>
<a mat-button role="button" [href]="section.call_to_action_link">
{{section.call_to_action}}
<div class="subhead">{{section().subhead}}</div>
<a mat-button role="button" [href]="section().call_to_action_link">
{{section().call_to_action}}
</a>
</section>
<div class="img-info">{{section.image_info}}</div>
<div class="img-info">{{section().image_info}}</div>
</div>
} @else {
<h1>{{section.headline}}</h1>
<h1>{{section().headline}}</h1>
}
5 changes: 2 additions & 3 deletions src/app/components/hero-section/hero-section.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, input } from '@angular/core';
import { HeroSection } from '../../models/page.model';

@Component({
Expand All @@ -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' });
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<div class="caption">{{img.title}}</div>
</div>
</ng-container>
@if(images.length > 1) {
@if(images().length > 1) {
<button mat-icon-button class="prev" aria-label="Zurück" (click)="slider.prev()">
<mat-icon fontIcon="chevron_left" />
</button>
Expand Down
12 changes: 6 additions & 6 deletions src/app/components/image-slider/image-slider.component.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<HTMLElement>;
images = input([] as SliderImage[]);
height = input('300px');
sliderRef = viewChild<ElementRef<HTMLElement>>('sliderRef');

slider!: KeenSliderInstance;
images$!: Promise<Omit<SliderImage, 'file'>[]>;

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: {
Expand Down
42 changes: 21 additions & 21 deletions src/app/components/input-section/input-section.component.html
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
<mat-card class="card">
<mat-card class="card" *ngIf="item() as definition">
<mat-card-header>
<mat-card-title class="title">
{{item.value.caption}}
{{definition.value.caption}}
</mat-card-title>
</mat-card-header>
<mat-card-content class="content">
@if(item.type === 'select') {
@if(definition.type === 'select') {
<mat-button-toggle-group class="select-group"
[ngClass]="{'wrap-options': item.value.multiline}"
[name]="item.value.id"
[multiple]="item.value.multiple"
[disabled]="disabled"
[ngModel]="value"
[ngClass]="{'wrap-options': definition.value.multiline}"
[name]="definition.value.id"
[multiple]="definition.value.multiple"
[disabled]="disabled()"
[ngModel]="value()"
(ngModelChange)="updateValue($event)">
@for(opt of item.value.options; track $index) {
@for(opt of definition.value.options; track $index) {
<mat-button-toggle [value]="opt">
{{opt}}
</mat-button-toggle>
}
</mat-button-toggle-group>
} @else if (item.type === 'textarea') {
} @else if (definition.type === 'textarea') {
<mat-form-field class="field" appearance="outline">
<textarea matInput
[name]="item.value.id"
[disabled]="disabled"
[placeholder]="item.value.placeholder"
[pattern]="item.value.validation"
[ngModel]="value"
[name]="definition.value.id"
[disabled]="disabled()"
[placeholder]="definition.value.placeholder"
[pattern]="definition.value.validation"
[ngModel]="value()"
(ngModelChange)="updateValue($event)"
></textarea>
<mat-error *ngIf="!valid">
{{validationMessage}}
</mat-error>
</mat-form-field>
} @else if (item.type === 'text') {
} @else if (definition.type === 'text') {
<mat-form-field class="field" appearance="outline">
<input type="text" matInput
[name]="item.value.id"
[disabled]="disabled"
[placeholder]="item.value.placeholder"
[pattern]="item.value.validation"
[ngModel]="value"
[name]="definition.value.id"
[disabled]="disabled()"
[placeholder]="definition.value.placeholder"
[pattern]="definition.value.validation"
[ngModel]="value()"
(ngModelChange)="updateValue($event)"/>
<mat-error *ngIf="!valid">
{{validationMessage}}
Expand Down
28 changes: 15 additions & 13 deletions src/app/components/input-section/input-section.component.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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<InputDefinition>();
disabled = model(false);
value = model<InputValue>(undefined, { alias: 'ngModel' });
change = output<InputValue>({ 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 {
Expand All @@ -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);
}
}
4 changes: 2 additions & 2 deletions src/app/components/input-stepper/input-stepper.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<section>
@for (item of definitions; track $index) {
@for (item of definitions(); track $index) {
<div [@next]="show($index)">
<app-input-section [item]="item" [name]="item.value.id" [disabled]="!isActive($index)"
[ngModel]="data[item.value.id]" (ngModelChange)="update($event, item.value.id)" />
[ngModel]="model(item)" (ngModelChange)="update($event, item.value.id)" />
</div>
}
<span class="footer">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -36,7 +36,8 @@ describe('InputStepperComponent', () => {
multiline: true
}
}
];
]);
fixture.componentRef.setInput('data', {});

fixture.detectChanges();
});
Expand Down
45 changes: 27 additions & 18 deletions src/app/components/input-stepper/input-stepper.component.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<InputValue> = {};
@Output() continue = new EventEmitter<ContinueEventArgs>();
@ViewChildren(InputSectionComponent) inputs!: QueryList<InputSectionComponent>;
export class InputStepperComponent implements AfterViewInit {
readonly definitions = input.required<InputDefinition[]>();
readonly data = model<UserDataItems<InputValue>>({});
readonly continue = output<ContinueEventArgs>();
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);
}

Expand All @@ -59,33 +60,41 @@ 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() },
});
}

private setDisabled() {
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;
}
}
7 changes: 3 additions & 4 deletions src/app/components/nav/nav.component.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/ui/expand/expand.component.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title class="header">
<span>{{title}}</span>
<span>{{title()}}</span>
</mat-panel-title>
<mat-panel-description *ngIf="description">
<span>{{description}}</span>
<mat-panel-description *ngIf="description()">
<span>{{description()}}</span>
</mat-panel-description>
</mat-expansion-panel-header>
<div class="content">
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/ui/expand/expand.component.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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<string>();
description = input('');
}
Loading

0 comments on commit 0815fab

Please sign in to comment.