feat(challenge16): di

This commit is contained in:
thomas
2023-03-06 15:33:47 +01:00
parent 4f529c9427
commit 011ca2f462
19 changed files with 1529 additions and 458 deletions

View File

@@ -33,6 +33,7 @@ This goal of this project is to help you get better at Angular and NgRx by resol
<a href="./apps/pipe-hard/README.md"><img src="https://img.shields.io/badge/10-utilities pipe-red" alt="utilities pipe"/></a>
<a href="./apps/scroll-cd/README.md"><img src="https://img.shields.io/badge/12-change detection -- scroll-orange" alt="change detection with scroll event"/></a>
<a href="./apps/styling/README.md"><img src="https://img.shields.io/badge/13-styling-orange" alt="styling"/></a>
<a href="./apps/di/README.md"><img src="https://img.shields.io/badge/16-di-red" alt="di"/></a>
</br>
<img src="https://img.shields.io/badge/Typescript--gray?logo=typescript" alt="Typescript"/>
@@ -61,7 +62,7 @@ This goal of this project is to help you get better at Angular and NgRx by resol
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@thomas.laforge"><img src="https://avatars.githubusercontent.com/u/30832608?s…00&u=6f0ad9676792f29fd7fe6e113df06213d384a813&v=4" width="100px;" alt="Thomas Laforge"/><br /><sub><b>Thomas Laforge</b></sub></a><br />15 🧩</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@thomas.laforge"><img src="https://avatars.githubusercontent.com/u/30832608?s…00&u=6f0ad9676792f29fd7fe6e113df06213d384a813&v=4" width="100px;" alt="Thomas Laforge"/><br /><sub><b>Thomas Laforge</b></sub></a><br />16 🧩</a></td>
</tr>
</tbody>
</table>

36
apps/di/.eslintrc.json Normal file
View File

@@ -0,0 +1,36 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
},
"extends": [
"plugin:@nrwl/nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
]
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"rules": {}
}
]
}

40
apps/di/README.md Normal file
View File

@@ -0,0 +1,40 @@
<h1>Dependancy Injection</h1>
> Author: Thomas Laforge
### Information
To successfully complete this challenge, you will need to have a good understanding of how Dependency Injection works inside Angular.
The goal is to provide the CurrencyService at the row level, so that each row displays the correct currency. Currently, the CurrencyService is only provided at the table level, which results in an error as the same currency is displayed for each row, despite each product having a different currency.
One way to achieve this is by adding a second argument to the pipe, but this is not allowed.
### Statement
- Your task is to display the correct currency for each row.
### Constraints:
- You cannot modify the pipe.
- You cannot wrap the row inside a component, as this will break the layout.
### Submitting your work
1. Fork the project
2. clone it
3. npm install
4. `npx nx serve di`
5. _...work on it_
6. Commit your work
7. Submit a PR with a title beginning with **Answer:16** that I will review and other dev can review.
<!-- TODO: add challenge number and project Name -->
<a href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A16+label%3Aanswer"><img src="https://img.shields.io/badge/-Solutions-green" alt="DI"/></a>
<!-- TODO: uncomment when done late -->
<!-- <a href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A16+label%3A"answer+author"'><img src="https://img.shields.io/badge/-Author solution-important" alt="DI solution author"/></a>
<a href="{Blog post url}" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/-Blog post explanation-blue" alt="DI blog article"/></a> -->
_You can ask any question on_ <a href="https://twitter.com/laforge_toma" target="_blank" rel="noopener noreferrer"><img src="./../../logo/twitter.svg" height=20px alt="twitter"/></a>

81
apps/di/project.json Normal file
View File

@@ -0,0 +1,81 @@
{
"name": "di",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/di/src",
"prefix": "app",
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:browser",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/di",
"index": "apps/di/src/index.html",
"main": "apps/di/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "apps/di/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["apps/di/src/favicon.ico", "apps/di/src/assets"],
"styles": [
"apps/di/src/styles.scss",
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/themes/lara-light-blue/theme.css",
"node_modules/primeng/resources/primeng.min.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "di:build:production"
},
"development": {
"browserTarget": "di:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "di:build"
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/di/**/*.ts", "apps/di/**/*.html"]
}
}
},
"tags": []
}

View File

@@ -0,0 +1,54 @@
/* eslint-disable @angular-eslint/directive-selector */
import { AsyncPipe, NgFor } from '@angular/common';
import { Component, Directive } from '@angular/core';
import { TableModule } from 'primeng/table';
import { CurrencyPipe } from './currency.pipe';
import { CurrencyService } from './currency.service';
import { Product, products } from './product.model';
interface ProductContext {
$implicit: Product;
}
@Directive({
selector: 'ng-template[pTemplate="body"]',
standalone: true,
})
export class ProductDirective {
static ngTemplateContextGuard(
dir: ProductDirective,
ctx: unknown
): ctx is ProductContext {
return true;
}
}
@Component({
standalone: true,
imports: [TableModule, CurrencyPipe, AsyncPipe, NgFor, ProductDirective],
providers: [CurrencyService],
selector: 'app-root',
template: `
<p-table [value]="products">
<ng-template pTemplate="header">
<tr>
<th *ngFor="let col of displayedColumns">
{{ col }}
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-product>
<tr>
<td>{{ product.name }}</td>
<td>{{ product.priceA | currency | async }}</td>
<td>{{ product.priceB | currency | async }}</td>
<td>{{ product.priceC | currency | async }}</td>
</tr>
</ng-template>
</p-table>
`,
})
export class AppComponent {
products = products;
displayedColumns = ['name', 'priceA', 'priceB', 'priceC'];
}

View File

@@ -0,0 +1,17 @@
import { inject, Pipe, PipeTransform } from '@angular/core';
import { map } from 'rxjs';
import { CurrencyService } from './currency.service';
@Pipe({
name: 'currency',
standalone: true,
})
export class CurrencyPipe implements PipeTransform {
currencyService = inject(CurrencyService);
transform(price: number) {
return this.currencyService.symbol$.pipe(
map((s) => `${String(price)}${s}`)
);
}
}

View File

@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { map } from 'rxjs';
export interface Currency {
name: string;
code: string;
symbol: string;
}
export const currency: Currency[] = [
{ name: 'Euro', code: 'EUR', symbol: '€' },
{ name: 'Dollar US', code: 'USD', symbol: 'US$' },
{ name: 'Dollar Autralien', code: 'AUD', symbol: 'AU$' },
{ name: 'Livre Sterling', code: 'GBP', symbol: '£' },
{ name: 'Dollar Canadien', code: 'CAD', symbol: 'CAD' },
];
@Injectable()
export class CurrencyService extends ComponentStore<{ code: string }> {
readonly code$ = this.select((state) => state.code);
readonly symbol$ = this.code$.pipe(
map((code) => currency.find((c) => c.code === code)?.symbol ?? code)
);
constructor() {
super({ code: 'EUR' });
}
}

View File

@@ -0,0 +1,55 @@
export interface Product {
name: string;
priceA: number;
priceB: number;
priceC: number;
currencyCode: string;
}
export const products: Product[] = [
{
name: 'bike',
priceA: 1000,
priceB: 2000,
priceC: 2200,
currencyCode: 'USD',
},
{ name: 'tent', priceA: 112, priceB: 120, priceC: 41, currencyCode: 'EUR' },
{
name: 'sofa',
priceA: 500,
priceB: 422,
priceC: 5000,
currencyCode: 'EUR',
},
{
name: 'watch',
priceA: 50,
priceB: 130,
priceC: 150,
currencyCode: 'AUD',
},
{
name: 'computer',
priceA: 1000,
priceB: 2200,
priceC: 3500,
currencyCode: 'GBP',
},
{ name: 'mug', priceA: 10, priceB: 15, priceC: 20, currencyCode: 'EUR' },
{
name: 'headset',
priceA: 100,
priceB: 150,
priceC: 220,
currencyCode: 'CAD',
},
{ name: 'cable', priceA: 5, priceB: 10, priceC: 15, currencyCode: 'EUR' },
{
name: 'table',
priceA: 100,
priceB: 20,
priceC: 500,
currencyCode: 'EUR',
},
];

View File

BIN
apps/di/src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

13
apps/di/src/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Di</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

4
apps/di/src/main.ts Normal file
View File

@@ -0,0 +1,4 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

1
apps/di/src/styles.scss Normal file
View File

@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

10
apps/di/tsconfig.app.json Normal file
View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"],
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {
"types": []
}
}

29
apps/di/tsconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": false,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.editor.json"
}
],
"extends": "../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -19,11 +19,15 @@
### Submitting your work
<!-- To replace
{Project name} by your project name
{challenge number} by the following number (To be find inside the main readme) -->
1. Fork the project
2. clone it
3. npm install
<!-- TODO: add you project app name directory -->
4. **nx serve {{project app name}}**
4. `npx nx serve {project app name}`
5. _...work on it_
6. Commit your work
<!-- TODO: add your challenge number -->

1600
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,8 @@
"@rx-angular/cdk": "^1.0.0-rc.4",
"@rx-angular/state": "^1.7.0",
"@rx-angular/template": "^1.0.0-rc.5",
"primeicons": "^6.0.1",
"primeng": "^15.2.0",
"rxjs": "~7.5.0",
"tailwindcss": "^3.2.1",
"tslib": "^2.3.0",