mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-10 04:43:03 -05:00
feat(challenge16): di
This commit is contained in:
@@ -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
36
apps/di/.eslintrc.json
Normal 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
40
apps/di/README.md
Normal 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
81
apps/di/project.json
Normal 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": []
|
||||
}
|
||||
54
apps/di/src/app/app.component.ts
Normal file
54
apps/di/src/app/app.component.ts
Normal 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'];
|
||||
}
|
||||
17
apps/di/src/app/currency.pipe.ts
Normal file
17
apps/di/src/app/currency.pipe.ts
Normal 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}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
29
apps/di/src/app/currency.service.ts
Normal file
29
apps/di/src/app/currency.service.ts
Normal 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' });
|
||||
}
|
||||
}
|
||||
55
apps/di/src/app/product.model.ts
Normal file
55
apps/di/src/app/product.model.ts
Normal 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',
|
||||
},
|
||||
];
|
||||
0
apps/di/src/assets/.gitkeep
Normal file
0
apps/di/src/assets/.gitkeep
Normal file
BIN
apps/di/src/favicon.ico
Normal 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
13
apps/di/src/index.html
Normal 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
4
apps/di/src/main.ts
Normal 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
1
apps/di/src/styles.scss
Normal 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
10
apps/di/tsconfig.app.json
Normal 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"]
|
||||
}
|
||||
7
apps/di/tsconfig.editor.json
Normal file
7
apps/di/tsconfig.editor.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"types": []
|
||||
}
|
||||
}
|
||||
29
apps/di/tsconfig.json
Normal file
29
apps/di/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
1600
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user