diff --git a/README.md b/README.md index cd8bb77..6d5fe7c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f ## Challenges -Check [all 37 challenges](https://angular-challenges.vercel.app/) +Check [all 38 challenges](https://angular-challenges.vercel.app/) ## Contributors ✨ diff --git a/apps/rxjs-catch-error/.eslintrc.json b/apps/rxjs-catch-error/.eslintrc.json new file mode 100644 index 0000000..b428c22 --- /dev/null +++ b/apps/rxjs-catch-error/.eslintrc.json @@ -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:@nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ] + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/apps/rxjs-catch-error/README.md b/apps/rxjs-catch-error/README.md new file mode 100644 index 0000000..d1808fd --- /dev/null +++ b/apps/rxjs-catch-error/README.md @@ -0,0 +1,13 @@ +# catchError + +> Author: Devesh Chaudhari + +### Run Application + +```bash +npx nx serve rxjs-catch-error +``` + +### Documentation and Instruction + +Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/rxjs/38-catch-error/). diff --git a/apps/rxjs-catch-error/jest.config.ts b/apps/rxjs-catch-error/jest.config.ts new file mode 100644 index 0000000..0f2fbb2 --- /dev/null +++ b/apps/rxjs-catch-error/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'rxjs-catch-error', + preset: '../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../coverage/apps/rxjs-catch-error', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/apps/rxjs-catch-error/project.json b/apps/rxjs-catch-error/project.json new file mode 100644 index 0000000..7c191a9 --- /dev/null +++ b/apps/rxjs-catch-error/project.json @@ -0,0 +1,95 @@ +{ + "name": "rxjs-catch-error", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "prefix": "app", + "sourceRoot": "apps/rxjs-catch-error/src", + "tags": [], + "targets": { + "build": { + "executor": "@angular-devkit/build-angular:browser", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/rxjs-catch-error", + "index": "apps/rxjs-catch-error/src/index.html", + "main": "apps/rxjs-catch-error/src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "apps/rxjs-catch-error/tsconfig.app.json", + "assets": [ + "apps/rxjs-catch-error/src/favicon.ico", + "apps/rxjs-catch-error/src/assets" + ], + "styles": ["apps/rxjs-catch-error/src/styles.scss"], + "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": "rxjs-catch-error:build:production" + }, + "development": { + "browserTarget": "rxjs-catch-error:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "executor": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "rxjs-catch-error:build" + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "apps/rxjs-catch-error/**/*.ts", + "apps/rxjs-catch-error/**/*.html" + ] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/rxjs-catch-error/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + } +} diff --git a/apps/rxjs-catch-error/src/app/app.component.css b/apps/rxjs-catch-error/src/app/app.component.css new file mode 100644 index 0000000..e4064b2 --- /dev/null +++ b/apps/rxjs-catch-error/src/app/app.component.css @@ -0,0 +1,32 @@ +body { + font-family: Arial, sans-serif; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + margin: 0; + } + .form-container { + text-align: center; + } + input { + padding: 8px; + margin-right: 8px; + border: 1px solid #ccc; + border-radius: 4px; + } + button { + padding: 8px 16px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + } + .response { + margin-left: 25%; + margin-top: 2%; + width: 50%; + text-align: center; + border: 1px solid #ccc; + } \ No newline at end of file diff --git a/apps/rxjs-catch-error/src/app/app.component.spec.ts b/apps/rxjs-catch-error/src/app/app.component.spec.ts new file mode 100644 index 0000000..3905d62 --- /dev/null +++ b/apps/rxjs-catch-error/src/app/app.component.spec.ts @@ -0,0 +1,8 @@ +import { render } from '@testing-library/angular'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + test('...', async () => { + await render(AppComponent); + }); +}); diff --git a/apps/rxjs-catch-error/src/app/app.component.ts b/apps/rxjs-catch-error/src/app/app.component.ts new file mode 100644 index 0000000..6974c93 --- /dev/null +++ b/apps/rxjs-catch-error/src/app/app.component.ts @@ -0,0 +1,59 @@ +import { Component, DestroyRef, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { FormsModule } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Subject, concatMap, map } from 'rxjs'; + +@Component({ + standalone: true, + imports: [CommonModule, FormsModule], + selector: 'app-root', + template: ` +
+ possible values: posts, comments, albums, photos, todos, users +
+
+ + +
+
+ {{ response | json }} +
+ `, + styleUrls: ['./app.component.css'], +}) +export class AppComponent { + submit$$ = new Subject(); + input = ''; + response: unknown; + private destroyRef = inject(DestroyRef); + constructor(private http: HttpClient) {} + ngOnInit() { + this.submit$$ + .pipe( + map(() => this.input), + concatMap((value) => + this.http.get(`https://jsonplaceholder.typicode.com/${value}/1`) + ), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe({ + next: (value) => { + console.log(value); + this.response = value; + }, + error: (error) => { + console.log(error); + this.response = error; + }, + complete: () => console.log('done'), + }); + } +} diff --git a/apps/rxjs-catch-error/src/app/app.config.ts b/apps/rxjs-catch-error/src/app/app.config.ts new file mode 100644 index 0000000..610704b --- /dev/null +++ b/apps/rxjs-catch-error/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { ApplicationConfig, importProvidersFrom } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; + +export const appConfig: ApplicationConfig = { + providers: [importProvidersFrom(HttpClientModule)], +}; diff --git a/apps/rxjs-catch-error/src/assets/.gitkeep b/apps/rxjs-catch-error/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/rxjs-catch-error/src/favicon.ico b/apps/rxjs-catch-error/src/favicon.ico new file mode 100644 index 0000000..317ebcb Binary files /dev/null and b/apps/rxjs-catch-error/src/favicon.ico differ diff --git a/apps/rxjs-catch-error/src/index.html b/apps/rxjs-catch-error/src/index.html new file mode 100644 index 0000000..07c9d66 --- /dev/null +++ b/apps/rxjs-catch-error/src/index.html @@ -0,0 +1,13 @@ + + + + + rxjs-catch-error + + + + + + + + diff --git a/apps/rxjs-catch-error/src/main.ts b/apps/rxjs-catch-error/src/main.ts new file mode 100644 index 0000000..514c89a --- /dev/null +++ b/apps/rxjs-catch-error/src/main.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err) +); diff --git a/apps/rxjs-catch-error/src/styles.scss b/apps/rxjs-catch-error/src/styles.scss new file mode 100644 index 0000000..77e408a --- /dev/null +++ b/apps/rxjs-catch-error/src/styles.scss @@ -0,0 +1,5 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* You can add global styles to this file, and also import other style files */ diff --git a/apps/rxjs-catch-error/src/test-setup.ts b/apps/rxjs-catch-error/src/test-setup.ts new file mode 100644 index 0000000..15de72a --- /dev/null +++ b/apps/rxjs-catch-error/src/test-setup.ts @@ -0,0 +1,2 @@ +import '@testing-library/jest-dom'; +import 'jest-preset-angular/setup-jest'; diff --git a/apps/rxjs-catch-error/tailwind.config.js b/apps/rxjs-catch-error/tailwind.config.js new file mode 100644 index 0000000..38183db --- /dev/null +++ b/apps/rxjs-catch-error/tailwind.config.js @@ -0,0 +1,14 @@ +const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind'); +const { join } = require('path'); + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'), + ...createGlobPatternsForDependencies(__dirname), + ], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/apps/rxjs-catch-error/tsconfig.app.json b/apps/rxjs-catch-error/tsconfig.app.json new file mode 100644 index 0000000..fff4a41 --- /dev/null +++ b/apps/rxjs-catch-error/tsconfig.app.json @@ -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"] +} diff --git a/apps/rxjs-catch-error/tsconfig.editor.json b/apps/rxjs-catch-error/tsconfig.editor.json new file mode 100644 index 0000000..8ae117d --- /dev/null +++ b/apps/rxjs-catch-error/tsconfig.editor.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*.ts"], + "compilerOptions": { + "types": ["jest", "node"] + } +} diff --git a/apps/rxjs-catch-error/tsconfig.json b/apps/rxjs-catch-error/tsconfig.json new file mode 100644 index 0000000..e01cf19 --- /dev/null +++ b/apps/rxjs-catch-error/tsconfig.json @@ -0,0 +1,32 @@ +{ + "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.spec.json" + }, + { + "path": "./tsconfig.editor.json" + } + ], + "extends": "../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/apps/rxjs-catch-error/tsconfig.spec.json b/apps/rxjs-catch-error/tsconfig.spec.json new file mode 100644 index 0000000..1a4817a --- /dev/null +++ b/apps/rxjs-catch-error/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node", "@testing-library/jest-dom"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/challenge-number.json b/challenge-number.json index 14d6f15..55abd21 100644 --- a/challenge-number.json +++ b/challenge-number.json @@ -1,6 +1,6 @@ { - "total": 37, - "🟢": 13, + "total": 38, + "🟢": 14, "🟠": 117, "🔴": 207 } diff --git a/docs/src/content/docs/challenges/angular-performance/37-ngfor-biglist.md b/docs/src/content/docs/challenges/angular-performance/37-ngfor-biglist.md index 6b181ae..647575f 100644 --- a/docs/src/content/docs/challenges/angular-performance/37-ngfor-biglist.md +++ b/docs/src/content/docs/challenges/angular-performance/37-ngfor-biglist.md @@ -6,7 +6,6 @@ challengeNumber: 37 command: performance-ngfor-biglist sidebar: order: 117 - badge: New --- ## Information diff --git a/docs/src/content/docs/challenges/rxjs/38-rxjs-catch-error.md b/docs/src/content/docs/challenges/rxjs/38-rxjs-catch-error.md new file mode 100644 index 0000000..dec6c92 --- /dev/null +++ b/docs/src/content/docs/challenges/rxjs/38-rxjs-catch-error.md @@ -0,0 +1,34 @@ +--- +title: 🟢 catchError +description: Challenge 38 is about learning obervable completion. +Author: Devesh Chaudhari +command: rxjs-catch-error +challengeNumber: 38 +sidebar: + order: 14 + badge: New +--- + +## Information + +### How to Use the Application + +Our application features a form with a text input box and a "Fetch" button. Upon clicking the "Fetch" button, data is retrieved from a [free API](https://jsonplaceholder.typicode.com/){:target="\_blank"}. + +The correct values for a successful response are limited to: posts, comments, albums, photos, todos, and users. Any other values will result in an error response. + +### Bug + +A bug has been identified in our application. Users are only able to successfully fetch data until an invalid request is sent. Once an error response is received, users are unable to send additional requests. + +### Learnings + +This application provides an opportunity to understand the correct placement of a [`catchError`](https://rxjs.dev/api/operators/catchError) operator. If placed incorrectly, the overall subscription will be completed, preventing users from sending more requests. The goal is to preserve the overall subscription by handling error notifications from inner observables appropriately. + +## Statement + +The goal is to use the catchError operator to handle error management inside your Rxjs stream. + +## Constraints + +Users should be able to log the value/error each time they click the "Fetch" button. diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 942ba79..52af64f 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -24,8 +24,8 @@ hero: import { Card, CardGrid } from '@astrojs/starlight/components'; - - This repository gathers 37 Challenges related to Angular, Nx, RxJS, Ngrx and Typescript. + + This repository gathers 38 Challenges related to Angular, Nx, RxJS, Ngrx and Typescript. These challenges resolve around real-life issues or specific features to elevate your skills.