mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-10 12:53:03 -05:00
feat(challenge16): di
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user