Skip to content

Commit

Permalink
feat(portal): use Angular Signals (Azure-Samples#230)
Browse files Browse the repository at this point in the history
* feat(portal): migrate to signals (Azure-Samples#223)

* chore: merging to test in code spaces

* fix: call discount signals in templates

---------

Co-authored-by: Sumit Parakh <sumit.thecoder@gmail.com>
Co-authored-by: Wassim Chegham <github@wassim.dev>
  • Loading branch information
3 people committed Jul 31, 2023
1 parent d96dcda commit 1f252a3
Show file tree
Hide file tree
Showing 20 changed files with 141 additions and 155 deletions.
8 changes: 2 additions & 6 deletions packages/portal/src/app/about/about.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core";
import { Component } from "@angular/core";
import { TextBlockComponent } from "../shared/text-block/text-block.component";

@Component({
Expand All @@ -8,10 +8,6 @@ import { TextBlockComponent } from "../shared/text-block/text-block.component";
standalone: true,
imports: [TextBlockComponent],
})
export class AboutComponent implements OnInit {
export class AboutComponent {
title = "About";

constructor() {}

ngOnInit(): void {}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<section class="checkout-container" [ngSwitch]="result">
<section class="checkout-container" [ngSwitch]="result()">
<section class="checkout-success" *ngSwitchCase="'success'">
<div class="material-symbols-outlined" aria-hidden="true">done</div>
<h1>Thank you for your booking!</h1>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import { Component, OnInit, inject, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { ReservationService } from '../shared/reservation.service';
Expand All @@ -12,16 +12,16 @@ import { ReservationService } from '../shared/reservation.service';
imports: [RouterModule, CommonModule, MatButtonModule],
})
export class CheckoutpageComponent implements OnInit {
result = 'error';
result = signal<CheckoutResult>('error');

private route = inject(ActivatedRoute);
private reservationService = inject(ReservationService);

ngOnInit(): void {
this.result = this.route.snapshot.queryParams['result'];
this.result.set(this.route.snapshot.queryParams['result']);

const reservationId = this.route.snapshot.queryParams['reservationId'];
if (reservationId && this.result === 'cancel') {
if (reservationId && this.result() === 'cancel') {
this.reservationService.cancelReservation(reservationId);
}
}
Expand Down
18 changes: 9 additions & 9 deletions packages/portal/src/app/profile/profile.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@ <h2 class="subtitle">My Profile</h2>
<aside class="aside-left">
<mat-card>
<mat-card-header>
<mat-card-title>{{ user.name }}</mat-card-title>
<mat-card-title>{{ user().name }}</mat-card-title>
<div mat-card-avatar>
<!-- <img class="user__avatar" [src]="user.avatar" alt="User avatar"> -->
<span class="material-symbols-outlined user__avatar"> account_circle </span>
</div>
<mat-card-subtitle>{{ user.role }}</mat-card-subtitle>
<mat-card-subtitle>{{ user().role }}</mat-card-subtitle>
</mat-card-header>
</mat-card>
</aside>

<section class="profile-body">
<mat-tab-group [selectedIndex]="selectedTabIndex">
<mat-tab-group [selectedIndex]="selectedTabIndex()">

<!-- favorites -->
<mat-tab label="Saved Listings">
<div class="tab-wrapper">
<p *ngIf="!listings || listings.length === 0">
<p *ngIf="!listings() || listings().length === 0">
You have no saved listings. <a routerLink="/home">Browse listings</a>
</p>

<mat-card
*ngFor="let listing of listings"
*ngFor="let listing of listings()"
[ngClass]="{ 'loading-background': !listing.title }"
[title]="listing?.title"
>
Expand Down Expand Up @@ -60,8 +60,8 @@ <h2 class="subtitle">My Profile</h2>
<mat-tab label="Payment History">
<div class="tab-wrapper">
<mat-list>
<mat-list-item *ngIf="payments.length === 0">No payments found.</mat-list-item>
<mat-list-item *ngFor="let payment of payments; trackBy: trackByPaymentId">
<mat-list-item *ngIf="payments().length === 0">No payments found.</mat-list-item>
<mat-list-item *ngFor="let payment of payments(); trackBy: trackByPaymentId">
<div mat-line class="payment-title">
{{ showAmount(payment.amount) }} {{ payment.currency }} (via {{ payment.provider }})
<span class="payment-status" [ngClass]="payment.status">{{ payment.status }}</span>
Expand All @@ -79,8 +79,8 @@ <h2 class="subtitle">My Profile</h2>
<mat-tab label="Reservations">
<div class="tab-wrapper">
<mat-list>
<mat-list-item *ngIf="reservations.length === 0">No reservations found.</mat-list-item>
<mat-list-item *ngFor="let reservation of reservations; trackBy: trackByReservationId">
<mat-list-item *ngIf="reservations().length === 0">No reservations found.</mat-list-item>
<mat-list-item *ngFor="let reservation of reservations(); trackBy: trackByReservationId">
<div mat-line class="reservation-title">
{{ reservation.title }} ({{ reservation.guests }}
<span [ngPlural]="reservation.guests">
Expand Down
30 changes: 15 additions & 15 deletions packages/portal/src/app/profile/profile.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit, inject } from "@angular/core";
import { Component, OnInit, inject, signal } from "@angular/core";
import { MatButtonModule } from "@angular/material/button";
import { MatCardModule } from "@angular/material/card";
import { MatIconModule } from "@angular/material/icon";
Expand All @@ -20,11 +20,11 @@ import { UserService } from "../shared/user/user.service";
imports: [CommonModule, RouterModule, MatCardModule, MatListModule, MatIconModule, MatButtonModule, MatTabsModule],
})
export class ProfileComponent implements OnInit {
user: User = {} as User;
listings: Listing[] = [];
reservations: Reservation[] = [];
payments: Payment[] = [];
selectedTabIndex = 0;
user = signal<User>({} as User);
listings = signal<Listing[]>([]);
reservations = signal<Reservation[]>([]);
payments = signal<Payment[]>([]);
selectedTabIndex = signal<number>(0);

private route = inject(ActivatedRoute);
private userService = inject(UserService);
Expand All @@ -36,13 +36,13 @@ export class ProfileComponent implements OnInit {

constructor() {
this.userService.user$.subscribe(user => {
this.user = user;
this.user.set(user);
});
}

async ngOnInit() {
this.route.data.subscribe(async data => {
this.user = data["user"];
this.user.set(data["user"]);
await this.listFavorites();
await this.listReservations();
await this.listPayments();
Expand All @@ -52,32 +52,32 @@ export class ProfileComponent implements OnInit {

this.route.paramMap.subscribe(async params => {
const tab = params.get("tab");
this.selectedTabIndex = tabs.indexOf(tab || "favorites");
this.selectedTabIndex.set(tabs.indexOf(tab || "favorites"));
});
}

async listFavorites() {
// this call allows us to get the count of favorites
this.listings = await this.favoriteService.countFavoritesByUser(this.user);
this.listings.set(await this.favoriteService.countFavoritesByUser(this.user()));

// this call allows us to get the actual listings
this.listings = await this.favoriteService.getFavoritesByUser(this.user);
this.listings.set(await this.favoriteService.getFavoritesByUser(this.user()));
}

async removeFavorite(listing: Listing) {
// remove the listing from the UI
this.listings = this.listings.filter(l => l.id !== listing.id);
this.listings.update(listings => listings.filter(l => l.id !== listing.id));

// remove the favorite from the database
await this.favoriteService.removeFavorite(listing, this.user);
await this.favoriteService.removeFavorite(listing, this.user());
}

async listReservations() {
this.reservations = await this.reservationService.getReservationsByUser(this.user);
this.reservations.set(await this.reservationService.getReservationsByUser(this.user()));
}

async listPayments() {
this.payments = await this.paymentService.getPaymentsByUser(this.user);
this.payments.set(await this.paymentService.getPaymentsByUser(this.user()));
}

async viewListing(listingId: string) {
Expand Down
46 changes: 23 additions & 23 deletions packages/portal/src/app/rentalpage/rentalpage.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,38 @@
<section class="listing-photos">
<main>
<div
[ngClass]="{ 'loading-background': isLoading }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing.photos[0] + ')' }"
[ngClass]="{ 'loading-background': isLoading() }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing().photos[0] + ')' }"
alt="Main listing photo"
></div>
</main>
<aside>
<div
[ngClass]="{ 'loading-background': isLoading }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing.photos[1] + ')' }"
[ngClass]="{ 'loading-background': isLoading() }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing().photos[1] + ')' }"
alt="Listing photo #1"
></div>
<div
[ngClass]="{ 'loading-background': isLoading }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing.photos[2] + ')' }"
[ngClass]="{ 'loading-background': isLoading() }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing().photos[2] + ')' }"
alt="Listing photo #2"
></div>
<div
[ngClass]="{ 'loading-background': isLoading }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing.photos[3] + ')' }"
[ngClass]="{ 'loading-background': isLoading() }"
[ngStyle]="{ 'background-image': 'url(/assets/images/' + listing().photos[3] + ')' }"
alt="Listing photo #3"
>
<span>+{{ (listing.photos.length || 3) - 3 }} Photos</span>
<span>+{{ (listing().photos.length || 3) - 3 }} Photos</span>
</div>
</aside>
</section>

<section class="listing-review" [ngClass]="{ 'loading-background': isLoading }">
<section class="listing-review" [ngClass]="{ 'loading-background': isLoading() }">
<main>
<div>{{ listing.reviews_number | i18nPlural : reviewsMapping }}</div>
<div>{{ listing().reviews_number | i18nPlural : reviewsMapping }}</div>
<div>
<span
*ngFor="let star of reviewStars"
*ngFor="let star of reviewStars()"
class="material-symbols-outlined listing-review__stars"
[ngClass]="{ fill: star === 1 }"
>
Expand All @@ -42,32 +42,32 @@
</div>
</main>
<aside>
<app-favorite-button *appHasRole="[userRole.Admin, userRole.Renter]" [listing]="listing"></app-favorite-button>
<app-favorite-button *appHasRole="[userRole.Admin, userRole.Renter]" [listing]="listing()"></app-favorite-button>

<button (click)="share()" title="Share listing" class="bookmark-listing">
<span aria-hidden="false" aria-label="Share listing" class="material-symbols-outlined"> share </span>
</button>
</aside>
</section>

<section class="listing-details" [ngClass]="{ 'loading-background': isLoading }">
<h1>{{ listing.title }}</h1>
<section class="listing-details" [ngClass]="{ 'loading-background': isLoading() }">
<h1>{{ listing().title }}</h1>
<p>
{{ listing.address[0] }}, {{ listing.address[1] }}, {{ listing.address[2] }},
{{ listing.address[3] }}
{{ listing().address[0] }}, {{ listing().address[1] }}, {{ listing().address[2] }},
{{ listing().address[3] }}
</p>
</section>

<section class="listing-description" [ngClass]="{ 'loading-background': isLoading }">
<section class="listing-description" [ngClass]="{ 'loading-background': isLoading() }">
<p>
{{ listing.description }}
{{ listing().description }}
</p>
</section>

<section class="listing-amenities" [ngClass]="{ 'loading-background': isLoading }">
<section class="listing-amenities" [ngClass]="{ 'loading-background': isLoading() }">
<h3>Features</h3>
<ul>
<li *ngFor="let feature of listing.ammenities">
<li *ngFor="let feature of listing().ammenities">
<span aria-hidden="false" aria-label="Listing ammenity" class="material-symbols-outlined">
{{ feature.split("|")[0] }}
</span>
Expand All @@ -77,6 +77,6 @@ <h3>Features</h3>
</section>
</section>

<section class="booking-container" [ngClass]="{ 'loading-background': isLoading }">
<app-booking-form [listing]="listing" (onRent)="onRent($event)"></app-booking-form>
<section class="booking-container" [ngClass]="{ 'loading-background': isLoading() }">
<app-booking-form [listing]="listing()" (onRent)="onRent($event)"></app-booking-form>
</section>
21 changes: 11 additions & 10 deletions packages/portal/src/app/rentalpage/rentalpage.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit, inject } from "@angular/core";
import { Component, OnInit, inject, signal } from "@angular/core";
import { ActivatedRoute, Navigation, Router } from "@angular/router";
import { BookingFormComponent } from "../shared/booking-form/booking-form.component";
import { FavoriteButtonComponent } from "../shared/favorite-button/favorite-button/favorite-button.component";
Expand All @@ -17,21 +17,22 @@ import { UserRole, UserService } from "../shared/user/user.service";
})
export class RentalpageComponent implements OnInit {
userRole: typeof UserRole = UserRole;
listing: Listing;
user: User;
navigation: Navigation | null;
reviewStars: number[] = [];
isLoading = true;
reviewsMapping: { [k: string]: string } = { "=0": "No reviews", "=1": "1 message", other: "# reviews" };

listing = signal<Listing>({} as Listing);
reviewStars = signal<number[]>([]);
isLoading = signal(true);

private router = inject(Router);
private route = inject(ActivatedRoute);
private listingService = inject(ListingService);
private userService = inject(UserService);

constructor() {
this.navigation = this.router.getCurrentNavigation();
this.listing = this.navigation?.extras.state?.["listing"] || null;
this.listing.set(this.navigation?.extras.state?.["listing"] || {});
this.user = this.navigation?.extras.state?.["user"] || null;
}

Expand All @@ -45,19 +46,19 @@ export class RentalpageComponent implements OnInit {
const listing = await this.listingService.getListingById(this.route.snapshot.params["id"]);

if (listing !== undefined) {
this.listing = listing;
this.isLoading = false;
this.listing.set(listing);
this.isLoading.set(false);
} else {
this.router.navigate(["/404"]);
}

this.reviewStars = Array(5)
this.reviewStars.set(Array(5)
.fill(0)
.map((x, i) => (i < this.listing?.reviews_stars ? 1 : 0));
.map((x, i) => (i < this.listing().reviews_stars ? 1 : 0)));
}

async share() {
await this.listingService.share(this.listing);
await this.listingService.share(this.listing());
}

async onRent(reservationDetails: ReservationRequest) {
Expand Down
4 changes: 2 additions & 2 deletions packages/portal/src/app/searchpage/searchpage.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
</div>
<div class="search-results">
<section class="featured-listings inner-wrapper">
<app-card-list [listings]="listings"></app-card-list>
<app-card-list [listings]="listings()"></app-card-list>
<div class="featured-listings__loading"></div>
<div *ngIf="seatrchTermInvalid" class="notification error">
<div *ngIf="seatrchTermInvalid()" class="notification error">
<p>{{ searchTermInvalidMessage }}</p>
</div>
<div *ngIf="noresults" class="notification info">
Expand Down
Loading

0 comments on commit 1f252a3

Please sign in to comment.