Skip to content

Commit d49cbc0

Browse files
authored
feat: challenge 54 pipe obs signal (tomalaforge#903)
1 parent e6fd546 commit d49cbc0

26 files changed

+417
-25
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f
2424
2525
## Challenges
2626

27-
Check [all 53 challenges](https://angular-challenges.vercel.app/)
27+
Check [all 54 challenges](https://angular-challenges.vercel.app/)
2828

2929
## Contributors ✨
3030

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"extends": ["../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts"],
7+
"extends": [
8+
"plugin:@nx/angular",
9+
"plugin:@angular-eslint/template/process-inline-templates"
10+
],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.html"],
15+
"extends": ["plugin:@nx/angular-template"],
16+
"rules": {}
17+
}
18+
]
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Pipe Observable to Signal
2+
3+
> author: thomas-laforge
4+
5+
### Run Application
6+
7+
```bash
8+
npx nx serve signal-pipe-observable-to-signal
9+
```
10+
11+
### Documentation and Instruction
12+
13+
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/signal/54-pipe-observable-to-signal/).
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"name": "signal-pipe-observable-to-signal",
3+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4+
"projectType": "application",
5+
"prefix": "app",
6+
"sourceRoot": "apps/signal/54-pipe-observable-to-signal/src",
7+
"tags": [],
8+
"targets": {
9+
"build": {
10+
"executor": "@angular-devkit/build-angular:application",
11+
"outputs": ["{options.outputPath}"],
12+
"options": {
13+
"outputPath": "dist/apps/signal/54-pipe-observable-to-signal",
14+
"index": "apps/signal/54-pipe-observable-to-signal/src/index.html",
15+
"browser": "apps/signal/54-pipe-observable-to-signal/src/main.ts",
16+
"polyfills": ["zone.js"],
17+
"tsConfig": "apps/signal/54-pipe-observable-to-signal/tsconfig.app.json",
18+
"inlineStyleLanguage": "scss",
19+
"assets": [
20+
"apps/signal/54-pipe-observable-to-signal/src/favicon.ico",
21+
"apps/signal/54-pipe-observable-to-signal/src/assets"
22+
],
23+
"styles": ["apps/signal/54-pipe-observable-to-signal/src/styles.scss"],
24+
"scripts": []
25+
},
26+
"configurations": {
27+
"production": {
28+
"budgets": [
29+
{
30+
"type": "initial",
31+
"maximumWarning": "500kb",
32+
"maximumError": "1mb"
33+
},
34+
{
35+
"type": "anyComponentStyle",
36+
"maximumWarning": "2kb",
37+
"maximumError": "4kb"
38+
}
39+
],
40+
"outputHashing": "all"
41+
},
42+
"development": {
43+
"optimization": false,
44+
"extractLicenses": false,
45+
"sourceMap": true
46+
}
47+
},
48+
"defaultConfiguration": "production"
49+
},
50+
"serve": {
51+
"executor": "@angular-devkit/build-angular:dev-server",
52+
"configurations": {
53+
"production": {
54+
"buildTarget": "signal-pipe-observable-to-signal:build:production"
55+
},
56+
"development": {
57+
"buildTarget": "signal-pipe-observable-to-signal:build:development"
58+
}
59+
},
60+
"defaultConfiguration": "development"
61+
},
62+
"extract-i18n": {
63+
"executor": "@angular-devkit/build-angular:extract-i18n",
64+
"options": {
65+
"buildTarget": "signal-pipe-observable-to-signal:build"
66+
}
67+
},
68+
"lint": {
69+
"executor": "@nx/eslint:lint"
70+
}
71+
}
72+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { TableComponent } from '@angular-challenges/shared/ui';
2+
import { AsyncPipe } from '@angular/common';
3+
import { ChangeDetectionStrategy, Component } from '@angular/core';
4+
import { CurrencyPipe } from './currency.pipe';
5+
import { ProductRowComponent } from './product-row.component';
6+
import { products } from './product.model';
7+
8+
@Component({
9+
standalone: true,
10+
imports: [AsyncPipe, CurrencyPipe, TableComponent, ProductRowComponent],
11+
selector: 'app-root',
12+
template: `
13+
<table [items]="products">
14+
<ng-template #header>
15+
<tr>
16+
@for (col of displayedColumns; track col) {
17+
<th>{{ col }}</th>
18+
}
19+
</tr>
20+
</ng-template>
21+
<ng-template #body let-product>
22+
<tr product-row [product]="product"></tr>
23+
</ng-template>
24+
</table>
25+
`,
26+
changeDetection: ChangeDetectionStrategy.OnPush,
27+
})
28+
export class AppComponent {
29+
products = products;
30+
displayedColumns = ['name', 'priceA', 'priceB', 'priceC'];
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { inject, Pipe, PipeTransform } from '@angular/core';
2+
import { map, Observable } from 'rxjs';
3+
import { CurrencyService } from './currency.service';
4+
5+
@Pipe({
6+
name: 'currency',
7+
standalone: true,
8+
})
9+
export class CurrencyPipe implements PipeTransform {
10+
currencyService = inject(CurrencyService);
11+
12+
transform(price: number): Observable<string> {
13+
return this.currencyService.symbol$.pipe(map((s) => `${price}${s}`));
14+
}
15+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Injectable } from '@angular/core';
2+
import { BehaviorSubject, map } from 'rxjs';
3+
4+
export interface Currency {
5+
name: string;
6+
code: string;
7+
symbol: string;
8+
}
9+
10+
export const currency: Currency[] = [
11+
{ name: 'Euro', code: 'EUR', symbol: '€' },
12+
{ name: 'Dollar US', code: 'USD', symbol: 'US$' },
13+
{ name: 'Dollar Autralien', code: 'AUD', symbol: 'AU$' },
14+
{ name: 'Livre Sterling', code: 'GBP', symbol: '£' },
15+
{ name: 'Dollar Canadien', code: 'CAD', symbol: 'CAD' },
16+
];
17+
18+
@Injectable()
19+
export class CurrencyService {
20+
private code = new BehaviorSubject('EUR');
21+
22+
readonly code$ = this.code.asObservable();
23+
readonly symbol$ = this.code$.pipe(
24+
map((code) => currency.find((c) => c.code === code)?.symbol ?? code),
25+
);
26+
27+
public updateCode(code: string) {
28+
this.code.next(code);
29+
}
30+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { AsyncPipe } from '@angular/common';
2+
import {
3+
ChangeDetectionStrategy,
4+
Component,
5+
inject,
6+
Input,
7+
} from '@angular/core';
8+
import { CurrencyPipe } from './currency.pipe';
9+
import { CurrencyService } from './currency.service';
10+
import { Product } from './product.model';
11+
12+
@Component({
13+
standalone: true,
14+
selector: 'tr[product-row]',
15+
template: `
16+
<td>{{ productInfo.name }}</td>
17+
<td>{{ productInfo.priceA | currency | async }}</td>
18+
<td>{{ productInfo.priceB | currency | async }}</td>
19+
<td>{{ productInfo.priceC | currency | async }}</td>
20+
`,
21+
imports: [AsyncPipe, CurrencyPipe],
22+
providers: [CurrencyService],
23+
changeDetection: ChangeDetectionStrategy.OnPush,
24+
})
25+
export class ProductRowComponent {
26+
protected productInfo!: Product;
27+
28+
@Input({ required: true }) set product(product: Product) {
29+
this.currencyService.updateCode(product.currencyCode);
30+
this.productInfo = product;
31+
}
32+
33+
currencyService = inject(CurrencyService);
34+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
export interface Product {
2+
name: string;
3+
priceA: number;
4+
priceB: number;
5+
priceC: number;
6+
currencyCode: string;
7+
}
8+
9+
export const products: Product[] = [
10+
{
11+
name: 'bike',
12+
priceA: 1000,
13+
priceB: 2000,
14+
priceC: 2200,
15+
currencyCode: 'USD',
16+
},
17+
{ name: 'tent', priceA: 112, priceB: 120, priceC: 41, currencyCode: 'EUR' },
18+
{
19+
name: 'sofa',
20+
priceA: 500,
21+
priceB: 422,
22+
priceC: 5000,
23+
currencyCode: 'EUR',
24+
},
25+
{
26+
name: 'watch',
27+
priceA: 50,
28+
priceB: 130,
29+
priceC: 150,
30+
currencyCode: 'AUD',
31+
},
32+
{
33+
name: 'computer',
34+
priceA: 1000,
35+
priceB: 2200,
36+
priceC: 3500,
37+
currencyCode: 'GBP',
38+
},
39+
{ name: 'mug', priceA: 10, priceB: 15, priceC: 20, currencyCode: 'EUR' },
40+
{
41+
name: 'headset',
42+
priceA: 100,
43+
priceB: 150,
44+
priceC: 220,
45+
currencyCode: 'CAD',
46+
},
47+
{ name: 'cable', priceA: 5, priceB: 10, priceC: 15, currencyCode: 'EUR' },
48+
{
49+
name: 'table',
50+
priceA: 100,
51+
priceB: 20,
52+
priceC: 500,
53+
currencyCode: 'EUR',
54+
},
55+
];

apps/signal/54-pipe-observable-to-signal/src/assets/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)