Skip to content

Commit ae416cb

Browse files
51 migrate playground app to signal based forms (#58)
1 parent 58fcac1 commit ae416cb

26 files changed

Lines changed: 266 additions & 153 deletions

apps/playground/db.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"operator": {
3232
"name": "Lufthansa",
3333
"shortName": "LH",
34-
"aircraftId": 1
34+
"aircraftId": "1"
3535
},
3636
"price": {
3737
"basePrice": 100,
@@ -55,19 +55,19 @@
5555
"operator": {
5656
"name": "Lufthansa",
5757
"shortName": "LH",
58-
"aircraftId": 2
58+
"aircraftId": "2"
5959
},
6060
"price": null
6161
}
6262
],
6363
"aircrafts": [
6464
{
65-
"id": 1,
65+
"id": "1",
6666
"registration": "D-EYPA",
6767
"type": "PA28"
6868
},
6969
{
70-
"id": 2,
70+
"id": "2",
7171
"registration": "D-AATW",
7272
"type": "A220-100"
7373
}

apps/playground/server.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ server.get('/views/flightSearchVm', (req, res) => {
4646
}
4747

4848
if (req.query.to) {
49-
flights = flights.fill(f => f.connection.to === req.query.to);
49+
flights = flights.filter(f => f.connection.to === req.query.to);
5050
}
5151

5252
if (req.query.from === undefined && req.query.to === undefined) {
@@ -88,7 +88,7 @@ server.get('/views/flightCreateVm', (req, res) => {
8888
operator: {
8989
name: '',
9090
shortName: '',
91-
aircraftId: 0
91+
aircraftId: ''
9292
}
9393
},
9494
aircrafts: db.aircrafts

apps/playground/src/app/flight/flight-create/flight-create.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
<div class="row">
88
<div class="col">
9-
<app-action-card #saveActionCard [disabled]="!saveEnabled()" (save)="onSaveFlight()">
10-
<app-flight-connection-form [model]="flightConnection()" (modelChange)="flightConnection.set($event)" />
11-
<app-flight-times-form [model]="flightTimes()" (modelChange)="flightTimes.set($event)" />
12-
<app-flight-operator-form [aircrafts]="aircrafts()" [model]="flightOperator()" (modelChange)="flightOperator.set($event)" />
9+
<app-action-card #saveActionCard [disabled]="!store.createFlightState.isAvailable()" (save)="onSaveFlight()">
10+
<app-flight-connection-form [connection]="templateForm.connection" />
11+
<app-flight-times-form [times]="templateForm.times" />
12+
<app-flight-operator-form [aircrafts]="store.flightCreateVm.aircrafts()" [operator]="templateForm.operator" />
1313
</app-action-card>
1414
</div>
1515
</div>

apps/playground/src/app/flight/flight-create/flight-create.component.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { FlightConnectionFormComponent } from '../shared/flight-connection-form/
55
import { FlightOperatorFormComponent } from '../shared/flight-operator-form/flight-operator-form.component';
66
import { FlightTimesFormComponent } from '../shared/flight-times-form/flight-times-form.component';
77
import { FlightCreateStore } from './flight-create.store';
8+
import { flightConnectionSchema, flightOperatorSchema, flightTimesSchema } from '../flight.entities';
9+
import { apply, form } from '@angular/forms/signals';
810

911
@Component({
1012
selector: 'app-flight-create',
@@ -14,16 +16,11 @@ import { FlightCreateStore } from './flight-create.store';
1416
export class FlightCreateComponent {
1517
location = inject(Location);
1618
store = inject(FlightCreateStore);
17-
18-
viewModel = this.store.getFlightCreateVmAsPatchable();
19-
20-
saveEnabled = this.store.createFlightState.isAvailable;
21-
22-
aircrafts = this.viewModel.aircrafts;
23-
24-
flightConnection = this.viewModel.template.connection;
25-
flightTimes = this.viewModel.template.times;
26-
flightOperator = this.viewModel.template.operator;
19+
templateForm = form(this.store.localTemplate, template => {
20+
apply(template.connection, flightConnectionSchema);
21+
apply(template.times, flightTimesSchema);
22+
apply(template.operator, flightOperatorSchema);
23+
});
2724

2825
async onSaveFlight() {
2926
await this.store.createFlight();

apps/playground/src/app/flight/flight-create/flight-create.store.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { withHypermediaAction, withLinkedHypermediaResource } from "@angular-architects/ngrx-hateoas";
1+
import { withHypermediaAction, withLinkedHypermediaResource, withWritableStateCopy } from "@angular-architects/ngrx-hateoas";
22
import { signalStore } from "@ngrx/signals";
33
import { FlightConnection, FlightTimes, FlightOperator, Aircraft, initialFlightConnection, initialFlightTimes, initialFlightOperator } from "../flight.entities";
44
import { FlightSearchStore } from "../flight-search/flight-search.store";
@@ -25,5 +25,8 @@ export const initialFlightCreateVm: FlightCreateVm = {
2525
export const FlightCreateStore = signalStore(
2626
{ providedIn: 'root' },
2727
withLinkedHypermediaResource('flightCreateVm', initialFlightCreateVm, () => inject(FlightSearchStore).flightSearchVm, 'flightCreateVm'),
28-
withHypermediaAction('createFlight', store => store.flightCreateVm.template, 'create')
28+
withWritableStateCopy(store => ({
29+
localTemplate: store.flightCreateVm.template
30+
})),
31+
withHypermediaAction('createFlight', store => store.localTemplate, 'create')
2932
);

apps/playground/src/app/flight/flight-edit/flight-edit.component.html

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,28 @@
44
</div>
55
</div>
66

7-
<pre>
8-
State
9-
{{ store.flightEditVm.flight.connection() | json }}
10-
WritableState
11-
{{ store.flightConnection() | json }}
12-
Local Flight Connection
13-
{{ localFlightConnection() | json }}
14-
</pre>
15-
167
<div class="row">
178
<div class="col">
189
<app-action-card #connection
1910
[disabled]="!store.updateFlightConnectionState.isAvailable()"
2011
(save)="onUpdateConnection()">
21-
<app-flight-connection-form [(model)]="flightConnection" />
12+
<app-flight-connection-form [connection]="flightForm.connection" />
2213
</app-action-card>
2314
<app-action-card #times
2415
[disabled]="!store.updateFlightTimesState.isAvailable()"
2516
(save)="onUpdateTimes()">
26-
<app-flight-times-form [(model)]="flightTimes" />
17+
<app-flight-times-form [times]="flightForm.times" />
2718
</app-action-card>
2819
<app-action-card #operator
2920
[disabled]="!store.updateFlightOperatorState.isAvailable()"
3021
(save)="onUpdateOperator()">
31-
<app-flight-operator-form [aircrafts]="store.flightEditVm.aircrafts()" [(model)]="flightOperator" />
22+
<app-flight-operator-form [aircrafts]="store.flightEditVm.aircrafts()" [operator]="flightForm.operator" />
3223
</app-action-card>
33-
@if(flightPrice(); as flightPriceValue) {
24+
@if(flightForm.price().value(); as flightPriceValue) {
3425
<app-action-card #price
3526
[disabled]="!store.updateFlightPriceState.isAvailable()"
3627
(save)="onUpdatePrice()">
37-
<app-flight-price-form [model]="flightPriceValue" (modelChange)="flightPrice.set($event)" />
28+
<app-flight-price-form [price]="$any(flightForm.price)" />
3829
</app-action-card>
3930
}
4031
</div>

apps/playground/src/app/flight/flight-edit/flight-edit.component.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
1-
import { Component, inject, linkedSignal, viewChild } from '@angular/core';
1+
import { Component, inject, viewChild } from '@angular/core';
22
import { ActionCardComponent } from '../../shared/ui/action-card/action-card.component';
33
import { FlightConnectionFormComponent } from '../shared/flight-connection-form/flight-connection-form.component';
44
import { FlightOperatorFormComponent } from '../shared/flight-operator-form/flight-operator-form.component';
55
import { FlightPriceFormComponent } from '../shared/flight-price-form/flight-price-form.component';
66
import { FlightTimesFormComponent } from '../shared/flight-times-form/flight-times-form.component';
77
import { FlightEditStore } from './flight-edit.store';
8-
import { JsonPipe } from "@angular/common";
8+
import { flightSchema } from '../flight.entities';
9+
import { form } from '@angular/forms/signals';
910

1011
@Component({
1112
selector: 'app-flight-edit',
12-
imports: [ActionCardComponent, FlightConnectionFormComponent, FlightOperatorFormComponent, FlightTimesFormComponent, FlightPriceFormComponent, JsonPipe],
13+
imports: [ActionCardComponent, FlightConnectionFormComponent, FlightOperatorFormComponent, FlightTimesFormComponent, FlightPriceFormComponent],
1314
templateUrl: './flight-edit.component.html'
1415
})
1516
export class FlightEditComponent {
1617
store = inject(FlightEditStore);
17-
flightConnection = this.store.flightConnection;
18-
localFlightConnection = linkedSignal(() => this.store.flightEditVm.flight.connection());
19-
flightTimes = this.store.flightTimes;
20-
flightOperator = this.store.flightOperator;
21-
flightPrice = this.store.flightPrice;
18+
flightForm = form(this.store.localFlight, flightSchema)
2219

2320
_connectionCard = viewChild.required<ActionCardComponent>('connection');
2421
_timesCard = viewChild.required<ActionCardComponent>('times');
@@ -36,7 +33,7 @@ export class FlightEditComponent {
3633

3734
onUpdateTimes() {
3835
try {
39-
this.store.updateFlightConnection();
36+
this.store.updateFlightTimes();
4037
this._timesCard().showSuccess();
4138
} catch {
4239
this._timesCard().showError();
@@ -45,7 +42,7 @@ export class FlightEditComponent {
4542

4643
onUpdateOperator() {
4744
try {
48-
this.store.updateFlightConnection();
45+
this.store.updateFlightOperator();
4946
this._operatorCard().showSuccess();
5047
} catch {
5148
this._operatorCard().showError();
@@ -54,7 +51,7 @@ export class FlightEditComponent {
5451

5552
onUpdatePrice() {
5653
try {
57-
this.store.updateFlightConnection();
54+
this.store.updateFlightPrice();
5855
this._priceCard()?.showSuccess();
5956
} catch {
6057
this._priceCard()?.showError();

apps/playground/src/app/flight/flight-edit/flight-edit.store.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@ export const FlightEditStore = signalStore(
1616
{ providedIn: 'root' },
1717
withHypermediaResource('flightEditVm', initialFlightEditVm),
1818
withWritableStateCopy(store => ({
19-
flightConnection: store.flightEditVm.flight.connection,
20-
flightTimes: store.flightEditVm.flight.times,
21-
flightOperator: store.flightEditVm.flight.operator,
22-
flightPrice: store.flightEditVm.flight.price
19+
localFlight: store.flightEditVm.flight
2320
})),
24-
withHypermediaAction('updateFlightConnection', store => store.flightConnection, 'update'),
25-
withHypermediaAction('updateFlightTimes', store => store.flightTimes, 'update'),
26-
withHypermediaAction('updateFlightOperator', store => store.flightOperator, 'update'),
27-
withHypermediaAction('updateFlightPrice', store => store.flightPrice, 'update')
21+
withHypermediaAction('updateFlightConnection', store => store.localFlight.connection, 'update'),
22+
withHypermediaAction('updateFlightTimes', store => store.localFlight.times, 'update'),
23+
withHypermediaAction('updateFlightOperator', store => store.localFlight.operator, 'update'),
24+
withHypermediaAction('updateFlightPrice', store => store.localFlight.price, 'update')
2825
);
2926

3027

apps/playground/src/app/flight/flight-search/flight-search.component.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div class="row">
33
<div class="col">
44
<span class="lead">Flight Search</span>
5-
@if(viewModel() | hasLink:'flightCreateVm') {
5+
@if(store.flightSearchVm() | hasLink:'flightCreateVm') {
66
<button class="btn btn-secondary float-end" [routerLink]="['/flight/create']">Create</button>
77
}
88
</div>
@@ -12,13 +12,13 @@
1212
<form #searchForm="ngForm">
1313
<div class="mb-3">
1414
<label for="from" class="form-label">From</label>
15-
<input class="form-control" id="from" name="from" [(ngModel)]="from" />
15+
<input class="form-control" id="from" name="from" [(ngModel)]="store.flightSearchVmFromDelegate" />
1616
</div>
1717
<div class="mb-3">
1818
<label for="to" class="form-label">To</label>
19-
<input class="form-control" id="to" name="to" [(ngModel)]="to" />
19+
<input class="form-control" id="to" name="to" [(ngModel)]="store.flightSearchVmToDelegate" />
2020
</div>
21-
@if(viewModel() | hasLink:'flightSearchVm') {
21+
@if(store.flightSearchVm() | hasLink:'flightSearchVm') {
2222
<button
2323
[disabled]="!searchForm.valid"
2424
class="btn btn-primary"
@@ -36,14 +36,14 @@
3636
<span class="lead pb-3">Search Results</span>
3737
</div>
3838

39-
@if(!viewModel.flights()) {
39+
@if(!store.flightSearchVm.flights()) {
4040
<div class="row">
4141
<p>Search for flights to show something here!</p>
4242
</div>
4343
} @else {
4444
<div class="row row-cols-1 row-cols-md-2 g-4">
4545

46-
@for(flight of viewModel.flights(); track flight.id) {
46+
@for(flight of store.flightSearchVm.flights(); track flight.id) {
4747
<div class="col">
4848
<app-flight-summary-card [flight]="flight" (delete)="onDelete(flight.id)">
4949
@if(flight | hasLink:'flightEditVm') {

apps/playground/src/app/flight/flight-search/flight-search.component.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,9 @@ export class FlightSearchComponent {
1414
hateoasService = inject(HateoasService);
1515
router = inject(Router);
1616
store = inject(FlightSearchStore);
17-
viewModel = this.store.getFlightSearchVmAsPatchable();
18-
from = this.viewModel['from'];
19-
to = this.viewModel['to'];
2017

2118
onSearch() {
22-
let url = this.hateoasService.getLink(this.viewModel(), 'flightSearchVm')?.href;
23-
url = url + '?from=' + (this.from() ?? '') + '&to=' + (this.to() ?? '');
19+
const url = this.hateoasService.getUrl(this.store.flightSearchVm(), 'flightSearchVm', { from: this.store.flightSearchVm.from() ?? '', to: this.store.flightSearchVm.to() ?? '' });
2420
this.router.navigate(['/flight/search', url]);
2521
}
2622

0 commit comments

Comments
 (0)