Merge pull request #193 from DeveshChau/rxjs-catch-error

feat: challenge 38 - rxjs catch error
This commit is contained in:
Laforge Thomas
2023-10-17 08:48:37 +02:00
committed by GitHub
24 changed files with 415 additions and 6 deletions

View File

@@ -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 ✨

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:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
]
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}

View File

@@ -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/).

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'rxjs-catch-error',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../coverage/apps/rxjs-catch-error',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/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',
],
};

View File

@@ -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
}
}
}
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,8 @@
import { render } from '@testing-library/angular';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
test('...', async () => {
await render(AppComponent);
});
});

View File

@@ -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: `
<div class="form-container">
<span
>possible values: posts, comments, albums, photos, todos, users</span
>
</div>
<form class="form-container" (ngSubmit)="submit$$.next()">
<input
type="text"
placeholder="Enter text"
[(ngModel)]="input"
name="action" />
<button>Fetch</button>
</form>
<div class="response">
{{ response | json }}
</div>
`,
styleUrls: ['./app.component.css'],
})
export class AppComponent {
submit$$ = new Subject<void>();
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'),
});
}
}

View File

@@ -0,0 +1,6 @@
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [importProvidersFrom(HttpClientModule)],
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>rxjs-catch-error</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>

View File

@@ -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)
);

View File

@@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* You can add global styles to this file, and also import other style files */

View File

@@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'jest-preset-angular/setup-jest';

View File

@@ -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: [],
};

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": ["jest", "node"]
}
}

View File

@@ -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
}
}

View File

@@ -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"
]
}

View File

@@ -1,6 +1,6 @@
{
"total": 37,
"🟢": 13,
"total": 38,
"🟢": 14,
"🟠": 117,
"🔴": 207
}

View File

@@ -6,7 +6,6 @@ challengeNumber: 37
command: performance-ngfor-biglist
sidebar:
order: 117
badge: New
---
## Information

View File

@@ -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.

View File

@@ -24,8 +24,8 @@ hero:
import { Card, CardGrid } from '@astrojs/starlight/components';
<CardGrid>
<Card title="37 Challenges">
This repository gathers 37 Challenges related to <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> and <b>Typescript</b>.
<Card title="38 Challenges">
This repository gathers 38 Challenges related to <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> and <b>Typescript</b>.
These challenges resolve around real-life issues or specific features to elevate your skills.
</Card>