From 2a8b8ea59ec82defbe2eb3d6d349c8ea54d438eb Mon Sep 17 00:00:00 2001 From: thomas Date: Mon, 26 Jun 2023 22:30:15 +0200 Subject: [PATCH] feat(challenge26): generator feature component --- README.md | 3 +- libs/custom-plugin/generators.json | 5 + .../generators/feature-component/README.md | 152 ++++++++++++++++++ .../generators/feature-component/generator.ts | 25 +++ .../generators/feature-component/schema.d.ts | 3 + .../generators/feature-component/schema.json | 18 +++ libs/fake-utils/.eslintrc.json | 36 +++++ libs/fake-utils/README.md | 3 + libs/fake-utils/ng-package.json | 7 + libs/fake-utils/package.json | 12 ++ libs/fake-utils/project.json | 36 +++++ libs/fake-utils/src/index.ts | 1 + libs/fake-utils/src/lib/base-url.token.ts | 3 + libs/fake-utils/tsconfig.json | 26 +++ libs/fake-utils/tsconfig.lib.json | 12 ++ libs/fake-utils/tsconfig.lib.prod.json | 7 + tsconfig.base.json | 1 + 17 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 libs/custom-plugin/src/generators/feature-component/README.md create mode 100644 libs/custom-plugin/src/generators/feature-component/generator.ts create mode 100644 libs/custom-plugin/src/generators/feature-component/schema.d.ts create mode 100644 libs/custom-plugin/src/generators/feature-component/schema.json create mode 100644 libs/fake-utils/.eslintrc.json create mode 100644 libs/fake-utils/README.md create mode 100644 libs/fake-utils/ng-package.json create mode 100644 libs/fake-utils/package.json create mode 100644 libs/fake-utils/project.json create mode 100644 libs/fake-utils/src/index.ts create mode 100644 libs/fake-utils/src/lib/base-url.token.ts create mode 100644 libs/fake-utils/tsconfig.json create mode 100644 libs/fake-utils/tsconfig.lib.json create mode 100644 libs/fake-utils/tsconfig.lib.prod.json diff --git a/README.md b/README.md index 2c7d9c5..05f3bab 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,8 @@ If you would like to propose a challenge, this project is open source, so feel f
nx -Create harness +extends lib generator +Custom component generator ## Contributors ✨ diff --git a/libs/custom-plugin/generators.json b/libs/custom-plugin/generators.json index 29d90c7..a80d29e 100644 --- a/libs/custom-plugin/generators.json +++ b/libs/custom-plugin/generators.json @@ -4,6 +4,11 @@ "factory": "./src/generators/custom-library/generator", "schema": "./src/generators/custom-library/schema.json", "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" } } } diff --git a/libs/custom-plugin/src/generators/feature-component/README.md b/libs/custom-plugin/src/generators/feature-component/README.md new file mode 100644 index 0000000..23363ca --- /dev/null +++ b/libs/custom-plugin/src/generators/feature-component/README.md @@ -0,0 +1,152 @@ +

Create a generator for a custom component

+ +> 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: ` // do things `, + 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 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( + 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(`${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. + +component generator +component generator solution author + + + +_You can ask any question on_ twitter diff --git a/libs/custom-plugin/src/generators/feature-component/generator.ts b/libs/custom-plugin/src/generators/feature-component/generator.ts new file mode 100644 index 0000000..ec073db --- /dev/null +++ b/libs/custom-plugin/src/generators/feature-component/generator.ts @@ -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; diff --git a/libs/custom-plugin/src/generators/feature-component/schema.d.ts b/libs/custom-plugin/src/generators/feature-component/schema.d.ts new file mode 100644 index 0000000..22ad619 --- /dev/null +++ b/libs/custom-plugin/src/generators/feature-component/schema.d.ts @@ -0,0 +1,3 @@ +export interface FeatureComponentGeneratorSchema { + name: string; +} diff --git a/libs/custom-plugin/src/generators/feature-component/schema.json b/libs/custom-plugin/src/generators/feature-component/schema.json new file mode 100644 index 0000000..79c7191 --- /dev/null +++ b/libs/custom-plugin/src/generators/feature-component/schema.json @@ -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"] +} diff --git a/libs/fake-utils/.eslintrc.json b/libs/fake-utils/.eslintrc.json new file mode 100644 index 0000000..f976439 --- /dev/null +++ b/libs/fake-utils/.eslintrc.json @@ -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": {} + } + ] +} diff --git a/libs/fake-utils/README.md b/libs/fake-utils/README.md new file mode 100644 index 0000000..a501f9d --- /dev/null +++ b/libs/fake-utils/README.md @@ -0,0 +1,3 @@ +# fake-utils + +This library was generated with [Nx](https://nx.dev). diff --git a/libs/fake-utils/ng-package.json b/libs/fake-utils/ng-package.json new file mode 100644 index 0000000..6f969c7 --- /dev/null +++ b/libs/fake-utils/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/libs/fake-utils", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/fake-utils/package.json b/libs/fake-utils/package.json new file mode 100644 index 0000000..8139e7b --- /dev/null +++ b/libs/fake-utils/package.json @@ -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 +} diff --git a/libs/fake-utils/project.json b/libs/fake-utils/project.json new file mode 100644 index 0000000..9d708d6 --- /dev/null +++ b/libs/fake-utils/project.json @@ -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" + ] + } + } + } +} diff --git a/libs/fake-utils/src/index.ts b/libs/fake-utils/src/index.ts new file mode 100644 index 0000000..cac301f --- /dev/null +++ b/libs/fake-utils/src/index.ts @@ -0,0 +1 @@ +export * from './lib/base-url.token'; diff --git a/libs/fake-utils/src/lib/base-url.token.ts b/libs/fake-utils/src/lib/base-url.token.ts new file mode 100644 index 0000000..4839448 --- /dev/null +++ b/libs/fake-utils/src/lib/base-url.token.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const BASE_URL = new InjectionToken('base_url'); diff --git a/libs/fake-utils/tsconfig.json b/libs/fake-utils/tsconfig.json new file mode 100644 index 0000000..8973c2e --- /dev/null +++ b/libs/fake-utils/tsconfig.json @@ -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 + } +} diff --git a/libs/fake-utils/tsconfig.lib.json b/libs/fake-utils/tsconfig.lib.json new file mode 100644 index 0000000..77b13c6 --- /dev/null +++ b/libs/fake-utils/tsconfig.lib.json @@ -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"] +} diff --git a/libs/fake-utils/tsconfig.lib.prod.json b/libs/fake-utils/tsconfig.lib.prod.json new file mode 100644 index 0000000..61b5237 --- /dev/null +++ b/libs/fake-utils/tsconfig.lib.prod.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": {} +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 04550ab..81073fa 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,6 +17,7 @@ "paths": { "@angular-challenges/cli": ["libs/cli/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": [ "libs/module-to-standalone/admin/feature/src/index.ts" ],