docs(docs): continue improving docs

This commit is contained in:
thomas
2023-09-25 09:14:29 +02:00
parent 73b6f2e68c
commit 97c3a6e198
38 changed files with 329 additions and 383 deletions

View File

@@ -8,10 +8,11 @@
## Intro
This project has been created with two purposes:
This project has been created with three purposes:
- The first purpose is to assist you in becoming better at Angular and its ecosystem. 💪
- The second purpose is to share best practices and different implementations of the same problem to gain diverse perspectives. 📖
- The last one is to lower the barrier to open source contribution.
**Sharing knowledge can benefit everyone.**
@@ -25,78 +26,6 @@ If you would like to propose a challenge, this project is open source, so feel f
Check [all 35 challenges](https://angular-challenges.vercel.app/)
## Challenges (previous version)
> Click the following badges to join your next challenge.
>
> <img src="https://img.shields.io/badge/Easy--green" alt="Easy challenge"/>
> <img src="https://img.shields.io/badge/Intermediate--orange" alt="Easy challenge"/>
> <img src="https://img.shields.io/badge/Advanced--red" alt="Easy challenge"/>
</br>
<img src="https://img.shields.io/badge/Angular--gray?logo=angular" alt="Angular"/>
<a href="./apps/projection/README.md"><img src="https://img.shields.io/badge/1-Projection-red" alt="Projection"/></a>
<a href="./apps/ngfor-enhancement/README.md"><img src="https://img.shields.io/badge/3-Directive enhancement-orange" alt="Directive enhancement"/></a>
<a href="./apps/context-outlet-type/README.md"><img src="https://img.shields.io/badge/4-ContextOutlet Typed-red" alt="Directive enhancement"/></a>
<a href="./apps/crud/README.md"><img src="https://img.shields.io/badge/5-CRUD-green" alt="crud application"/></a>
<a href="./apps/permissions/README.md"><img src="https://img.shields.io/badge/6-permissions-orange" alt="permissions"/></a>
<a href="./apps/pipe-easy/README.md"><img src="https://img.shields.io/badge/8-simple pure pipe-green" alt="pipe easy"/></a>
<a href="./apps/pipe-intermediate/README.md"><img src="https://img.shields.io/badge/9-wrapFn pipe-orange" alt="wrapFn pipe"/></a>
<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>
<a href="./apps/anchor-scrolling/README.md"><img src="https://img.shields.io/badge/21-anchor--scrolling-green" alt="anchor-scrolling"/></a>
<a href="./apps/router-input/README.md"><img src="https://img.shields.io/badge/22-router--input-green" alt="router-input"/></a>
<a href="./apps/interop-rxjs-signal/README.md"><img src="https://img.shields.io/badge/30-interop rxjs signal-red" alt="interop signal rxjs"/></a>
<a href="./apps/module-to-standalone/README.md"><img src="https://img.shields.io/badge/31-module to standalone-green" alt="module to standalone"/></a>
<a href="./apps/bug-cd/README.md"><img src="https://img.shields.io/badge/32-bug CD-orange" alt="bug CD"/></a>
<a href="./apps/decoupling/README.md"><img src="https://img.shields.io/badge/33-decoupling-orange" alt="decoupling"/></a>
</br>
<img src="https://img.shields.io/badge/Angular performance--gray?logo=angular" alt="Angular performance"/>
<a href="./apps/performance/default-onpush/README.md"><img src="https://img.shields.io/badge/34-Default vs OnPush-green" alt="default onPush"/></a>
</br>
<img src="https://img.shields.io/badge/Typescript--gray?logo=typescript" alt="Typescript"/>
<a href="./apps/overload/README.md"><img src="https://img.shields.io/badge/15-overload function-orange" alt="Overload function"/></a>
</br>
<img src="https://img.shields.io/badge/RxJs--gray?logo=reactivex" alt="RxJs"/>
<a href="./apps/rxjs-pipe-bug/README.md"><img src="https://img.shields.io/badge/11-BUG: chaining operators-orange" alt="Bug rxjs chaining operators"/></a>
<a href="./apps/rxjs-race-condition/README.md"><img src="https://img.shields.io/badge/14-race condition-green" alt="Rxjs race condition"/></a>
<!-- <a href="./apps/declarative-to-reactive/README.md"><img src="https://img.shields.io/badge/13-declarative to reactive-green" alt="Declarative to reactive programming"/></a> -->
</br>
<img src="https://img.shields.io/badge/NgRx--gray?logo=reactivex" alt="NgRx"/>
<a href="./apps/ngrx-1/README.md"><img src="https://img.shields.io/badge/2-Effect vs Selector-orange" alt="Effect vs Selector"/></a>
<a href="./apps/ngrx-notification/README.md"><img src="https://img.shields.io/badge/7-Power of Effects-red" alt="power of Effects"/></a>
</br>
<img src="https://img.shields.io/badge/Testing--gray?logo=cypress" alt="testing"/>
<a href="./apps/testing-router-outlet/README.md"><img src="https://img.shields.io/badge/17-Router Testing-orange" alt="router outlet Testing"/></a>
<a href="./apps/testing-nested/README.md"><img src="https://img.shields.io/badge/18-Nested Comp Testing-orange" alt="nested component Testing"/></a>
<a href="./apps/testing-input-output/README.md"><img src="https://img.shields.io/badge/19-Input Output Testing-orange" alt="input output Testing"/></a>
<a href="./apps/testing-modal/README.md"><img src="https://img.shields.io/badge/20-Modal Testing-orange" alt="modal Testing"/></a>
<a href="./apps/testing-harness/README.md"><img src="https://img.shields.io/badge/23-Harness Testing-green" alt="harness Testing"/></a>
<a href="./apps/create-harness/README.md"><img src="https://img.shields.io/badge/24-Create Harness-orange" alt="Create harness"/></a>
<a href="./apps/testing-checkbox/README.md"><img src="https://img.shields.io/badge/28-Checkbox Testing-green" alt="Test a simple checkbox"/></a>
<a href="./apps/testing-todos-list/README.md"><img src="https://img.shields.io/badge/29-Real application Testing-red" alt="Test a real application"/></a>
</br>
<img src="https://img.shields.io/badge/Nx--gray?logo=nx" alt="nx"/>
<a href="./libs/custom-plugin/src/generators/custom-library/README.md"><img src="https://img.shields.io/badge/25-Extends Nx Library generator-red" alt="extends lib generator"/></a>
<a href="./libs/custom-plugin/src/generators/feature-component/README.md"><img src="https://img.shields.io/badge/26-Component generator-orange" alt="Custom component generator"/></a>
<a href="./tools/eslint-rules/rules/forbidden-enum.README.md"><img src="https://img.shields.io/badge/27-Forbid enum Rule-green" alt="Forbid enum rules"/></a>
## Contributors ✨
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
@@ -122,15 +51,6 @@ Check [all 35 challenges](https://angular-challenges.vercel.app/)
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
Contributions of any kind are welcome.
## License

View File

@@ -4,9 +4,9 @@
<!-- TODO: add Information/Statement/Rules/Constraint/Steps -->
### Information
## Information
### Statement
## Statement
### Step 1

View File

@@ -2,7 +2,7 @@
> Author: Thomas Laforge
### Statement:
## Statement:
NOT IMPLEMENTED YET

View File

@@ -2,7 +2,7 @@
> Author: Thomas Laforge
### Statement:
## Statement:
NOT IMPLEMENTED YET

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #12</div>
### Information
## Information
In this challenge, you will need to optimize the change detection cycles run by Angular.
@@ -22,11 +22,11 @@ The following video will explain what is the goal of this challenge.
<video controls src="https://user-images.githubusercontent.com/30832608/209819211-58d9ddcf-e1ad-4a78-8a7a-2be9d729e3f1.mov">
</video>
### Statement
## Statement
Your goal for this challenge is to avoid all unnecessary change detection cycles and trigger a CD only when needed.
#### Constraint:
## Constraint:
You cannot opt-out of zone.js. If this code is part of a large project and you opt out of zone.js, you will break many things within your application.

View File

@@ -11,15 +11,15 @@ WIP
This challenge is inspired by a real-life example that I simplified to create this nice challenge.
### Information
## Information
In this small application, we have a navigation menu to route our application to either `barComponent` or `FooComponent`. However our application is not loading and no errors are displayed inside the console.
### Statement
## Statement
The goal of the challenge is to debug this application and make it work.
#### Hints
## Hints
<details>
<summary>Hint 1</summary>

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #34</div>
### Information
## Information
In this series of challenges, you will learn how to optimize and enhance the performance of your Angular Application.
@@ -27,11 +27,11 @@ If you click on one of the bars (indicated by the yellow arrow on the picture be
![profiler record](../../../../assets/34/profiler-record.png 'Profiler Record')
### Statement
## Statement
The goal of this challenge is to improve the clustering of change detection within the application.
### Hints:
## Hints:
<details>
<summary>Hint 1</summary>

View File

@@ -13,11 +13,11 @@ The goal of this serie of 3 pipe challenges is to master PIPES in Angular.
Pure pipe are a very useful way to transform data from your template. The difference between calling a function and a pipe is that pure pire are memoized. So they won't be recalculated every change detection cycle if the inputs hasn't changed.
### Information:
## Information:
In this third exercice, you want to access utils functions. Currently we cannot access them directly from your template. The goal is to create a specific pipe for this utils file where you will need to pass the name of the function you want to call and the needed arguments.
### Constraints:
## Constraints:
- must be strongly typed

View File

@@ -9,6 +9,8 @@ WIP
<div class="chip">Challenge #13</div>
## Information
Styling is an important part of a day job of a frontend developer often underestimated. In Angular application, I often see people use `@Input()` to customize the style of their component. But `@Input()` should be used only for the logic and we should use other technique for styling. We can take advantage of css variable and host-context.
In this challenge, you will need to use both to delete all `@Input()` from your code.
@@ -17,7 +19,7 @@ Styling is an important aspect of a frontend developer's day job, but it is ofte
In this challenge, you will need to use both CSS variables and :host-context to remove all `@Input()` from your code.
### Constraints:
## Constraints:
- In your final submission, your component should not contain any lines of code. All styling should be handled within the decorator _(or external css files if you prefer)_

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #16</div>
### Information
## Information
To successfully complete this challenge, you will need to have a good understanding of how Dependency Injection works inside Angular.
@@ -17,11 +17,11 @@ The goal is to provide the CurrencyService at the row level, so that each row di
One way to achieve this is by adding a second argument to the pipe, but this is not allowed.
### Statement
## Statement
- Your task is to display the correct currency for each row.
### Constraints:
## Constraints:
- You cannot modify the pipe.
- You cannot wrap the row inside a component, as this will break the layout.

View File

@@ -9,11 +9,11 @@ WIP
<div class="chip">Challenge #21</div>
### Information
## Information
You begin with an application that has basic navigation and anchor navigation in the `HomeComponent`. However, using `href` recreates the path each time and refreshes the page.
### Statement
## Statement
- Your task is to refactor this application to use the built-in navigation tool to better fit within the Angular Framework. You can explore the router, but it's better to stay within the template and use the `RouterLink` directive.
- To improve the user experience, add smooth scrolling.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #22</div>
### Statement
## Statement
In this small application, you can pass data though routing to `TestComponent`. v16 of Angular introduiced `RouterInput`. The goal of this exercice is to refactor the code to use the new `RouterInput` strategy.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #30</div>
### Information
## Information
In this challenge, we have a small reactive application using RxJS and NgRx/Component-Store.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #31</div>
### Information
## Information
In v14, standalone components were released and made stable in v15. If you haven't played with them, it's never too late. You can try them out in this challenge.
@@ -19,7 +19,7 @@ Finally, standalone components are very simple to understand, but routing/lazy-l
After completing this challenge, standalone components will no longer hold any secrets for you.
### Statement
## Statement
The goal of this challenge is to migrate your application from module based components to standalone components.

View File

@@ -12,7 +12,7 @@ WIP
> Big thanks to **Robin Goetz** and his [Spartan Project](https://github.com/goetzrobin/spartan).
> This challenge was proposed by Robin and is strongly inspired by his project.
### Information
## Information
The goal of this challenge is to separate the behavior of a component from its style. For the purpose of this challenge, we will be working on a button element. When we click on it, we will toggle a _disabled_ property which will change the style of the element. This is quite useless in real life but the challenge aims to demonstate a useful concept.
@@ -20,7 +20,7 @@ The behavior of the component (referred to as the _brain_ in the Spartan stack)
However the button's helmet needs to access the state of the component to style the button differently based on its state. As mention above, we cannot import the `BtnDisabledDirective` directly into the helmet library as done currently. If you go to [`BtnHelmetDirective`](../../libs/decoupling/helmet/src/lib/btn-style.directive.ts), you will encounter a linting error. **A project tagged with "type:hlm" can only depend on libs tagged with "type:core"**.
### Statement
## Statement
The goal of this challenge is to find a way to decouple both Directives.

View File

@@ -9,13 +9,13 @@ WIP
<div class="chip">Challenge #6</div>
### Information
## Information
Structural directive is an important concept you will need to master to improve your angular skills and knowledge. This will be the first part of this challenge.
Guard is also very important since you will always need it in every application you build.
### Statement
## Statement
In LoginComponent, you will find 6 buttons corresponding at 6 differents users.
@@ -27,11 +27,11 @@ In LoginComponent, you will find 6 buttons corresponding at 6 differents users.
- Client
- Everyone
### Step 1
## Step 1
In **InformationComponent**, display the correct piece of information for each roles.
#### Constraints:
### Constraints:
- no ngIf directive inside **InformationComponent**
- importing the store inside **InformationComponent** is not allowed.
@@ -50,7 +50,7 @@ You should end up with something like below:
<div *hasRoleSuperAdmin="true">Info Only for superadmin</div>
```
### Step 2
## Step 2
In **Routes.ts**, route all user to the correct **DashboardComponent** using **CanMatch** guard.

View File

@@ -13,11 +13,11 @@ The goal of this serie of 3 pipe challenges is to master PIPES in Angular.
Pure pipe are a very useful way to transform data from your template. The difference between calling a function and a pipe is that pure pire are memoized. So they won't be recalculated every change detection cycle if the inputs hasn't changed.
### Information:
## Information:
In this first exercice, you add calling a simple function inside your template. The goal is to convert it to a pipe.
### Constraints:
## Constraints:
- must be strongly typed

View File

@@ -13,12 +13,12 @@ The goal of this serie of 3 pipe challenges is to master PIPES in Angular.
Pure pipe are a very useful way to transform data from your template. The difference between calling a function and a pipe is that pure pire are memoized. So they won't be recalculated every change detection cycle if the inputs hasn't changed.
### Information:
## Information:
In this second exercice, you are calling multiple functions inside your template. You can create a specific pipe for each of the functions but this will be too cumbersome.
The goal is to create a `wrapFn` pipe to wrap your callback function though a pipe. Your function MUST remain inside your component. `WrapFn` must be highly reusable.
### Constraints:
## Constraints:
- must be strongly typed

View File

@@ -21,11 +21,9 @@ In NgRx, **selectors** is a very powerful tool often **misused**. You should use
## Statement
##### You will have to
You will have to Refactor this working example of a dashboard of activities.
1. Refactor this working example of a dashboard of activities.
##### Rules:
## Contraints:
- Only **one action** should be dispatched from a component
- Status effect is useless. Using **combineLatest** should be a red flag. And Effect are made for side effect, not transforming data. That's a selector role

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #7</div>
### Information
## Information
NgRx Effect is a very powerful library develop by the NgRx team. Effects subscribe to a HOT Observable and listen to any event dispatch from any place inside the application.
@@ -49,7 +49,7 @@ Your PR title must start with <b>Answer:7</b>.
❖ Community Answers
</a>
<a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A{challenge number}+label%3A'
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A7+label%3A'
alt="Power of Effect solution author">
▶︎ Author Answer
</a>

View File

@@ -4,5 +4,69 @@ description: Challenge 25 is about creating a Nx generator to extend the built-i
---
:::note
WIP: go [here](https://github.com/tomalaforge/angular-challenges/blob/main/libs/custom-plugin/src/generators/custom-library/README.md) if you want to do this challenge
WIP
:::
## Information
Welcome to the marvelous world of Nx generators.
Generators are awesome tools that can help you and your team generate code more quickly, especially for pieces of code that you use frequently. While using Nx, you create libraries regularly, but sometimes the default generator doesn't perfectly meet your needs.
## Statement
The goal of this challenge is to create a generator that extends the default library generator of Nx. You will need to override the default `jest.config.ts` and a `eslintrc.json` with a custom one.
You can either use all the default parameters of the Nx library generator or choose to modify some and keep others as defaults. The choice is yours.
## Constraints:
You should only override the jest configuration is the `unitTestRunner` option is set at `JEST`, and you should only update the eslint configuration if the `linter` is set to `eslint`.
---
`jest.config.ts`
```ts
/* eslint-disable */
export default {
displayName: '< libName >', // 👈 lib name
preset: '../../../jest.preset.js', // 👈 be careful with the path
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!(.*\\.mjs$|lodash-es))'],
};
```
---
`eslintrc.json`
add this rule `"@typescript-eslint/member-ordering": "off"` inside the rules properties of ts files.
---
:::tip[Reminder]
Your PR title must start with <b>Answer:25</b>.
:::
<div class="article-footer">
<a
href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A25+label%3Aanswer"
alt="Extend Lib Generator community solutions">
❖ Community Answers
</a>
<a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A25+label%3A'
alt="Extend Lib Generator solution author">
▶︎ Author Answer
</a>
</div>

View File

@@ -4,5 +4,157 @@ description: Challenge 26 is about creating a Nx generator to create a custom co
---
:::note
WIP: go [here](https://github.com/tomalaforge/angular-challenges/blob/main/libs/custom-plugin/src/generators/feature-component/README.md) if you want to do this challenge
WIP
:::
## Information
Welcome to the marvelous world of Nx generators.
Generators are awesome tools that can help you and your team generate code more quickly, especially for pieces of code that you use frequently. Inside an entreprise project, you often have to create components that look similar. And most of the time, you end up copy/pasting other components. In Nx, you can create this boilerplate in a simple command using generators.
## Statement
The goal of this challenge is to create a generator that will create all the boilerplate of a component for you.
Just below, you will have the end result of your generator for a `UserComponent` associated with a `@ngrx/component-store`.
## Options
- name : name of your component/store/service
- createService: flag to tell if a http service should be created
- yes : create as below
- no: don't create the inject/import/effect/function call (anything related to the service call)
- inlineTemplate: flag to decide if template should be inline or in a separate file
---
`user.component.ts`
```ts
@Component({
selector: 'app-user',
standalone: true,
imports: [LetDirective],
providers: [provideComponentStore(UserStore)],
template: ` <ng-container *ngrxLet="vm$ as vm"> // do things </ng-container> `,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserComponent {
private userStore = inject(UserStore);
readonly vm$ = this.userStore.vm$;
}
```
---
`user.store.json`
```ts
import { Injectable, inject } from '@angular/core';
import { ComponentStore, OnStateInit, OnStoreInit, tapResponse } from '@ngrx/component-store';
import { mergeMap, pipe, tap } from 'rxjs';
import { User } from './user.model';
import { UserService } from './user.service';
export interface UserState {
users: User[];
loading: boolean;
error?: string;
}
const initialState: UserState = {
users: [],
loading: false,
error: undefined,
};
@Injectable()
export class UserStore extends ComponentStore<UserState> implements OnStateInit, OnStoreInit {
private userService = inject(UserService);
private readonly users$ = this.select((state) => state.users);
private readonly loading$ = this.select((state) => state.loading);
private readonly error$ = this.select((state) => state.error);
readonly vm$ = this.select(
{
users: this.users$,
loading: this.loading$,
error: this.error$,
},
{ debounce: true }
);
ngrxOnStateInit() {
this.setState(initialState);
}
ngrxOnStoreInit() {
this.loadUsers();
}
readonly loadUsers = this.effect<void>(
pipe(
tap(() => this.patchState({ loading: true })),
mergeMap(() =>
this.userService.loadUsers().pipe(
tapResponse(
(users) => this.patchState({ users, loading: false }),
(err: string) => this.patchState({ error: err, loading: false })
)
)
)
)
);
}
```
---
`user.service.ts`
```ts
import { BASE_URL } from '@angular-challenges/fake-utils';
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { User } from './user.model';
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private BASE_URL = inject(BASE_URL);
loadUsers = () => this.http.get<User[]>(`${this.BASE_URL}/users`);
}
```
---
`user.model.ts`
```ts
export interface User {
name: string;
}
```
---
:::tip[Reminder]
Your PR title must start with <b>Answer:26</b>.
:::
<div class="article-footer">
<a
href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A26+label%3Aanswer"
alt="Component Generator community solutions">
❖ Community Answers
</a>
<a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A26+label%3A'
alt="Component Generator solution author">
▶︎ Author Answer
</a>
</div>

View File

@@ -4,5 +4,38 @@ description: Challenge 27 is about creating a custom Eslint Rule to forbid enums
---
:::note
WIP: go [here](https://github.com/tomalaforge/angular-challenges) if you want to do this challenge
WIP
:::
## Information
Eslint is an amazing tool that helps developers avoid simple mistakes and adhere to company style guides.
In this first example, we will create a rule that forbids the use of enums. The rule will suggest using string unions instead of enums whenever you add an enum to your code. It is a straightforward rule for learning how to create rules.
You will also need to write tests to verify the rule's functionality.
To test the rule inside your project, add `"@nrwl/nx/workspace/forbidden-enum": "error"` to the `eslintrc.json` file and attempt to insert an enum into any project to witness the magic. 😇
To assist you with AST (Abstract Syntax Tree) definitions, you can visit the [AST explorer](https://astexplorer.net/) and use JavaScript, @typescript-eslint/parser, and Eslint-v8 as the transformation method. However, please note that you will only get the type information there. The transformation function may not work for TypeScript types since the editor is in JavaScript.
You can also check this [repo](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin/src/rules) for eslint rule examples.
---
:::tip[Reminder]
Your PR title must start with <b>Answer:27</b>.
:::
<div class="article-footer">
<a
href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A27+label%3Aanswer"
alt="Custom Eslint Rule community solutions">
❖ Community Answers
</a>
<a
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A27+label%3A'
alt="Custom Eslint Rule solution author">
▶︎ Author Answer
</a>
</div>

View File

@@ -13,13 +13,13 @@ Let's dive inside the wonderful word of RxJs.
This challenge is inspired by a real-life example.
### Presentation of the challenge
## Presentation of the challenge
#### User Story
### User Story
We need a button for each `Topic`. When we click on it, we delete all objects with this `Topic` in our database _(Fake DB in our case)_. Finally we display **All [topic] have been deleted** is everything was deleted successfully or **Error: deletion of some [topic] failed** if some deletions failed
#### Constraints:
### Constraints:
We can only pass one object to our DB for deletion at the time. The DB will respond true if the data was successfully deleted and false otherwise.

View File

@@ -9,20 +9,20 @@ WIP
<div class="chip">Challenge #14</div>
### Information
## Information
The goal of this application is to display a list of topics in a modal when a button is clicked. The application functions correctly. However, your tech lead has asked you to add tests and they are failing.
### Statement
## Statement
Correct your application to pass the test
### Constraints:
## Constraints:
- I can see you coming 🤣 => You CANNOT change the test (Test is working fine) 😳
- You CANNOT change the `fakeGetHttpTopic` method. A delay has been added to fake a slow network.
### Run the test
## Run the test
HEADLESS : `npx nx component-test rxjs-race-condition`
WATCH MODE : `npx nx component-test rxjs-race-condition --watch`

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #17</div>
### Information
## Information
Testing is a crucial step in building scalable, maintainable, and trustworthy applications.
Testing should never be avoided, even in the face of short deadlines or strong pressure from the product team.
@@ -17,7 +17,7 @@ Nowadays, there are numerous awesome tools available that make it easy to test y
In this series of testing exercises, we will learn and master [Testing Library](https://testing-library.com/docs/) and [Cypress Component Testing](https://docs.cypress.io/guides/component-testing/angular/overview) that simplifies DOM manipulation for testing any Angular component.
### Statement:
## Statement:
We have a functional application that lists available books for borrowing inside a library. If the book you searched is available, you will be directed to the corresponding book(s), otherwise, you will end up on an error page.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #18</div>
### Statement:
## Statement:
We have a small application that send a title to a fake backend that you type inside a input.
If the title is correctly typed, you can send the request otherwise you get a nice error and the request is not sent.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #19</div>
### Statement:
## Statement:
We have a small counter application that increment or decrement a number.
You can play with it by running : `npx nx serve testing-input-output`.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #20</div>
### Statement:
## Statement:
The goal of this challenge is to test dialogs inside your application.
Within this program, you will get an error modal if the user doesn't input a name, while a confirmation modal will appear in all other cases.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #23</div>
### Statement:
## Statement:
The objective of this challenge is to have a better understanding of the CDK test harness API. In this initial challenge, we will only use Angular Material's built-in harnesses.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #24</div>
### Information
## Information
The goal of this challenge is to create a test harness for `slider.component.ts`. The harness file, `slider.harness.ts`, has already been created.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #28</div>
### Information
## Information
This is the perfect example to get started with `Testing Library`.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #29</div>
### Statement:
## Statement:
I built this more real life application to create more real life test cases.
In this application, you can search for tickets, you can assign or finish them. You can also create new tickets.

View File

@@ -9,7 +9,7 @@ WIP
<div class="chip">Challenge #15</div>
### Information
## Information
Angular uses TypeScript, and mastering TypeScript can help you avoid runtime errors by catching them at compile time.
@@ -19,7 +19,7 @@ One solution would be to create a separate function for each vehicle type, but f
To achieve this, we will use overload functions.
### Statement
## Statement
- Use function overload

View File

@@ -7,9 +7,9 @@
<!-- TODO: add Information/Statement/Rules/Constraint/Steps -->
### Information
## Information
### Statement
## Statement
### Step 1

View File

@@ -1,64 +1,7 @@
<h1>Create a generator to extend @nx/angular-lib</h1>
# Extend Lib Generator
> Author: Thomas Laforge
### Information
### Documentation and Instruction
Welcome to the marvelous world of Nx generators.
Generators are awesome tools that can help you and your team generate code more quickly, especially for pieces of code that you use frequently. While using Nx, you create libraries regularly, but sometimes the default generator doesn't perfectly meet your needs.
### Statement
The goal of this challenge is to create a generator that extends the default library generator of Nx. You will need to override the default `jest.config.ts` and a `eslintrc.json` with a custom one.
You can either use all the default parameters of the Nx library generator or choose to modify some and keep others as defaults. The choice is yours.
### Constraints:
You should only override the jest configuration is the `unitTestRunner` option is set at `JEST`, and you should only update the eslint configuration if the `linter` is set to `eslint`.
---
`jest.config.ts`
```ts
/* eslint-disable */
export default {
displayName: '< libName >', // 👈 lib name
preset: '../../../jest.preset.js', // 👈 be careful with the path
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!(.*\\.mjs$|lodash-es))'],
};
```
---
`eslintrc.json`
add this rule `"@typescript-eslint/member-ordering": "off"` inside the rules properties of ts files.
### Submitting your work
1. Fork the project
2. clone it
3. npm ci
4. _...work on it_
5. Commit your work
6. Submit a PR with a title beginning with **Answer:25** that I will review and other dev can review.
<a href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A25+label%3Aanswer"><img src="https://img.shields.io/badge/-Solutions-green" alt="extends-lib"/></a>
<a href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A25+label%3A"answer+author"'><img src="https://img.shields.io/badge/-Author solution-important" alt="extends-lib 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="extends-lib 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>
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/nx/25-generator-lib-ext.md/).

View File

@@ -1,152 +1,7 @@
<h1>Create a generator for a custom component</h1>
# Component Generator
> Author: Thomas Laforge
### Information
### Documentation and Instruction
Welcome to the marvelous world of Nx generators.
Generators are awesome tools that can help you and your team generate code more quickly, especially for pieces of code that you use frequently. Inside an entreprise project, you often have to create components that look similar. And most of the time, you end up copy/pasting other components. In Nx, you can create this boilerplate in a simple command using generators.
### Statement
The goal of this challenge is to create a generator that will create all the boilerplate of a component for you.
Just below, you will have the end result of your generator for a `UserComponent` associated with a `@ngrx/component-store`.
#### Options
- name : name of your component/store/service
- createService: flag to tell if a http service should be created
- yes : create as below
- no: don't create the inject/import/effect/function call (anything related to the service call)
- inlineTemplate: flag to decide if template should be inline or in a separate file
---
`user.component.ts`
```ts
@Component({
selector: 'app-user',
standalone: true,
imports: [LetDirective],
providers: [provideComponentStore(UserStore)],
template: ` <ng-container *ngrxLet="vm$ as vm"> // do things </ng-container> `,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserComponent {
private userStore = inject(UserStore);
readonly vm$ = this.userStore.vm$;
}
```
---
`user.store.json`
```ts
import { Injectable, inject } from '@angular/core';
import { ComponentStore, OnStateInit, OnStoreInit, tapResponse } from '@ngrx/component-store';
import { mergeMap, pipe, tap } from 'rxjs';
import { User } from './user.model';
import { UserService } from './user.service';
export interface UserState {
users: User[];
loading: boolean;
error?: string;
}
const initialState: UserState = {
users: [],
loading: false,
error: undefined,
};
@Injectable()
export class UserStore extends ComponentStore<UserState> implements OnStateInit, OnStoreInit {
private userService = inject(UserService);
private readonly users$ = this.select((state) => state.users);
private readonly loading$ = this.select((state) => state.loading);
private readonly error$ = this.select((state) => state.error);
readonly vm$ = this.select(
{
users: this.users$,
loading: this.loading$,
error: this.error$,
},
{ debounce: true }
);
ngrxOnStateInit() {
this.setState(initialState);
}
ngrxOnStoreInit() {
this.loadUsers();
}
readonly loadUsers = this.effect<void>(
pipe(
tap(() => this.patchState({ loading: true })),
mergeMap(() =>
this.userService.loadUsers().pipe(
tapResponse(
(users) => this.patchState({ users, loading: false }),
(err: string) => this.patchState({ error: err, loading: false })
)
)
)
)
);
}
```
---
`user.service.ts`
```ts
import { BASE_URL } from '@angular-challenges/fake-utils';
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { User } from './user.model';
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private BASE_URL = inject(BASE_URL);
loadUsers = () => this.http.get<User[]>(`${this.BASE_URL}/users`);
}
```
---
`user.model.ts`
```ts
export interface User {
name: string;
}
```
### Submitting your work
1. Fork the project
2. clone it
3. npm ci
4. _...work on it_
5. Commit your work
6. Submit a PR with a title beginning with **Answer:26** that I will review and other dev can review.
<a href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A26+label%3Aanswer"><img src="https://img.shields.io/badge/-Solutions-green" alt="component generator"/></a>
<a href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A26+label%3A"answer+author"'><img src="https://img.shields.io/badge/-Author solution-important" alt="component generator 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="extends-lib 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>
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/nx/26-generator-comp.md/).

View File

@@ -1,34 +1,13 @@
<h1>Create a eslint rule to forbid enums</h1>
# Custom Eslint Rule
> Author: Thomas Laforge
### Information
### Run Application
Eslint is an amazing tool that helps developers avoid simple mistakes and adhere to company style guides.
```bash
npx nx serve eslint-rules
```
In this first example, we will create a rule that forbids the use of enums. The rule will suggest using string unions instead of enums whenever you add an enum to your code. It is a straightforward rule for learning how to create rules.
### Documentation and Instruction
You will also need to write tests to verify the rule's functionality.
To test the rule inside your project, add `"@nrwl/nx/workspace/forbidden-enum": "error"` to the `eslintrc.json` file and attempt to insert an enum into any project to witness the magic. 😇
To assist you with AST (Abstract Syntax Tree) definitions, you can visit the [AST explorer](https://astexplorer.net/) and use JavaScript, @typescript-eslint/parser, and Eslint-v8 as the transformation method. However, please note that you will only get the type information there. The transformation function may not work for TypeScript types since the editor is in JavaScript.
You can also check this [repo](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin/src/rules) for eslint rule examples.
### Submitting your work
1. Fork the project
2. clone it
3. npm ci
4. _...work on it_
5. `npx nx test eslint-rules`
6. Commit your work
7. Submit a PR with a title beginning with **Answer:27** that I will review and other dev can review.
<a href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A27+label%3Aanswer"><img src="https://img.shields.io/badge/-Solutions-green" alt="forbidden-enum"/></a>
<a href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A27+label%3A"answer+author"'><img src="https://img.shields.io/badge/-Author solution-important" alt="forbidden-enum 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="forbidden-enum 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>
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/nx/27-forbid-enum-rule.md/).