mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-10 12:53:03 -05:00
feat(challenge26): generator feature component
This commit is contained in:
@@ -78,7 +78,8 @@ If you would like to propose a challenge, this project is open source, so feel f
|
|||||||
</br>
|
</br>
|
||||||
<img src="https://img.shields.io/badge/Nx--gray?logo=nx" alt="nx"/>
|
<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="Create harness"/></a>
|
<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/25-Component generator-orange" alt="Custom component generator"/></a>
|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
"factory": "./src/generators/custom-library/generator",
|
"factory": "./src/generators/custom-library/generator",
|
||||||
"schema": "./src/generators/custom-library/schema.json",
|
"schema": "./src/generators/custom-library/schema.json",
|
||||||
"description": "extends library from nx/cli"
|
"description": "extends library from nx/cli"
|
||||||
|
},
|
||||||
|
"feature-component": {
|
||||||
|
"factory": "./src/generators/feature-component/generator",
|
||||||
|
"schema": "./src/generators/feature-component/schema.json",
|
||||||
|
"description": "feature-component generator"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
152
libs/custom-plugin/src/generators/feature-component/README.md
Normal file
152
libs/custom-plugin/src/generators/feature-component/README.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<h1>Create a generator for a custom component</h1>
|
||||||
|
|
||||||
|
> Author: Thomas Laforge
|
||||||
|
|
||||||
|
### 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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
formatFiles,
|
||||||
|
generateFiles,
|
||||||
|
Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { FeatureComponentGeneratorSchema } from './schema';
|
||||||
|
|
||||||
|
export async function featureComponentGenerator(
|
||||||
|
tree: Tree,
|
||||||
|
options: FeatureComponentGeneratorSchema
|
||||||
|
) {
|
||||||
|
const projectRoot = `libs/${options.name}`;
|
||||||
|
addProjectConfiguration(tree, options.name, {
|
||||||
|
root: projectRoot,
|
||||||
|
projectType: 'library',
|
||||||
|
sourceRoot: `${projectRoot}/src`,
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
generateFiles(tree, path.join(__dirname, 'files'), projectRoot, options);
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default featureComponentGenerator;
|
||||||
3
libs/custom-plugin/src/generators/feature-component/schema.d.ts
vendored
Normal file
3
libs/custom-plugin/src/generators/feature-component/schema.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface FeatureComponentGeneratorSchema {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "FeatureComponent",
|
||||||
|
"title": "",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What name would you like to use?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"]
|
||||||
|
}
|
||||||
36
libs/fake-utils/.eslintrc.json
Normal file
36
libs/fake-utils/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "attribute",
|
||||||
|
"prefix": "lib",
|
||||||
|
"style": "camelCase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"prefix": "lib",
|
||||||
|
"style": "kebab-case"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:@nx/angular",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"extends": ["plugin:@nx/angular-template"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
libs/fake-utils/README.md
Normal file
3
libs/fake-utils/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# fake-utils
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
7
libs/fake-utils/ng-package.json
Normal file
7
libs/fake-utils/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||||
|
"dest": "../../dist/libs/fake-utils",
|
||||||
|
"lib": {
|
||||||
|
"entryFile": "src/index.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
libs/fake-utils/package.json
Normal file
12
libs/fake-utils/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@angular-challenges/fake-utils",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^16.0.0",
|
||||||
|
"@angular/core": "^16.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"sideEffects": false
|
||||||
|
}
|
||||||
36
libs/fake-utils/project.json
Normal file
36
libs/fake-utils/project.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "fake-utils",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/fake-utils/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"tags": [],
|
||||||
|
"projectType": "library",
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@nx/angular:ng-packagr-lite",
|
||||||
|
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"project": "libs/fake-utils/ng-package.json"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"tsConfig": "libs/fake-utils/tsconfig.lib.prod.json"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"tsConfig": "libs/fake-utils/tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"libs/fake-utils/**/*.ts",
|
||||||
|
"libs/fake-utils/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
libs/fake-utils/src/index.ts
Normal file
1
libs/fake-utils/src/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './lib/base-url.token';
|
||||||
3
libs/fake-utils/src/lib/base-url.token.ts
Normal file
3
libs/fake-utils/src/lib/base-url.token.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const BASE_URL = new InjectionToken<string>('base_url');
|
||||||
26
libs/fake-utils/tsconfig.json
Normal file
26
libs/fake-utils/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
12
libs/fake-utils/tsconfig.lib.json
Normal file
12
libs/fake-utils/tsconfig.lib.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": ["src/**/*.spec.ts", "jest.config.ts", "src/**/*.test.ts"],
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
7
libs/fake-utils/tsconfig.lib.prod.json
Normal file
7
libs/fake-utils/tsconfig.lib.prod.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.lib.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declarationMap": false
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@angular-challenges/cli": ["libs/cli/src/index.ts"],
|
"@angular-challenges/cli": ["libs/cli/src/index.ts"],
|
||||||
"@angular-challenges/custom-plugin": ["libs/custom-plugin/src/index.ts"],
|
"@angular-challenges/custom-plugin": ["libs/custom-plugin/src/index.ts"],
|
||||||
|
"@angular-challenges/fake-utils": ["libs/fake-utils/src/index.ts"],
|
||||||
"@angular-challenges/module-to-standalone/admin/feature": [
|
"@angular-challenges/module-to-standalone/admin/feature": [
|
||||||
"libs/module-to-standalone/admin/feature/src/index.ts"
|
"libs/module-to-standalone/admin/feature/src/index.ts"
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user