diff --git a/.gitignore b/.gitignore index d64b3a45a..6c63120f6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ junit.xml **/.cache**/ .nx/cache -.nx/workspace-data \ No newline at end of file +.nx/workspace-data +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md diff --git a/.global/integration-app.style.scss b/.global/integration-app.style.scss index 8e61095ca..374d89f10 100644 --- a/.global/integration-app.style.scss +++ b/.global/integration-app.style.scss @@ -1,44 +1,35 @@ @use '@angular/material' as mat; -$theme: mat.define-theme( +@include mat.core(); + +$ng-app-variable-primary: mat.m2-define-palette(mat.$m2-indigo-palette); +$ng-app-variable-accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400); +$ng-app-variable-warn: mat.m2-define-palette(mat.$m2-red-palette); + +$ng-app-variable-theme: mat.m2-define-light-theme( ( color: ( - theme-type: dark, - primary: mat.$violet-palette, - ), - typography: ( - brand-family: 'Comic Sans', - bold-weight: 900, - ), - density: ( - scale: -1, + primary: $ng-app-variable-primary, + accent: $ng-app-variable-accent, + warn: $ng-app-variable-warn, ), + typography: mat.m2-define-typography-config(), + density: -1, ) ); -@include mat.core(); +@include mat.all-component-themes($ng-app-variable-theme); +@include mat.typography-hierarchy($ng-app-variable-theme); +@include mat.form-field-density(-4); html, body { height: 100%; - - @include mat.core-theme($theme); - @include mat.button-theme($theme); - @include mat.core-color($theme); - @include mat.button-color($theme); } body { margin: 0; - font-family: - pfhighway, - -apple-system, - BlinkMacSystemFont, - Segoe UI, - Roboto, - Helvetica, - Arial, - sans-serif; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } a { @@ -54,9 +45,11 @@ a { } .dashboard__menu { - background: rgb(24, 75, 170); - color: #fff; - width: 300px; + --mat-sidenav-container-background-color: rgb(24, 75, 170); + --mat-sidenav-container-text-color: #fff; + --mat-sidenav-container-width: 300px; + --mat-toolbar-container-background-color: var(--mat-sidenav-container-background-color); + --mat-toolbar-container-text-color: var(--mat-sidenav-container-text-color); } .title { @@ -95,7 +88,7 @@ a { color: red; } -.spinner.mat-progress-spinner { +.spinner.mat-mdc-progress-spinner { position: absolute; right: 0; } @@ -124,7 +117,7 @@ code { } .mat-divider { - border-top-color: rgba(241, 232, 232, 0.12); + --mat-divider-color: rgba(241, 232, 232, 0.12); width: 80%; margin: 0 auto !important; } diff --git a/README.md b/README.md index 15ac5f407..edf0ba202 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,19 @@ found in the Angular. --- +### Migration to v19 + +Check out the [migration guide](docs/introduction/migration-v19.md) when migrating to `v19` from `v18` or earlier. + +### Compatiblity + +| Angular | cdk / ngxs | Standalone Components | +| ------- | ---------- | --------------------- | +| 20+ | 19 | ✔️ | +| 18+ | 18 | ❌ | + +### Packages + | **Package** | **Version** | **README** | **Downloads** | | -------------------------------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | [@angular-ru/cdk](https://npmjs.com/package/@angular-ru/cdk) | ![](https://img.shields.io/npm/v/%40angular-ru%2Fcdk/latest.svg) | [![](https://img.shields.io/badge/Documentation--green.svg)](https://angular-ru.gitbook.io/sdk/cdk) | [![](https://img.shields.io/npm/dw/@angular-ru/cdk)](https://npmjs.com/package/@angular-ru/cdk) | diff --git a/apps/cdk-demo/main.ts b/apps/cdk-demo/main.ts index 7f04f8cab..bd35bd4f6 100644 --- a/apps/cdk-demo/main.ts +++ b/apps/cdk-demo/main.ts @@ -1,13 +1,8 @@ -import {enableProdMode} from '@angular/core'; -import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {bootstrapApplication} from '@angular/platform-browser'; -import {environment} from './environments/environment'; -import {AppModule} from './src/app.module'; +import {AppComponent} from './src/app.component'; +import {appConfig} from './src/app.config'; -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic() - .bootstrapModule(AppModule, {ngZoneEventCoalescing: true}) - .catch((error: Error): void => console.error(error)); +bootstrapApplication(AppComponent, appConfig).catch((error: unknown) => + console.error(error), +); diff --git a/apps/cdk-demo/polyfills.ts b/apps/cdk-demo/polyfills.ts deleted file mode 100644 index 59e6a6b7f..000000000 --- a/apps/cdk-demo/polyfills.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '@angular-ru/cdk/zone.js/unpatch-zone'; -import 'zone.js'; diff --git a/apps/cdk-demo/project.json b/apps/cdk-demo/project.json index c39eded3f..42ddf7ac4 100644 --- a/apps/cdk-demo/project.json +++ b/apps/cdk-demo/project.json @@ -6,59 +6,58 @@ "tags": ["apps"], "targets": { "build": { - "executor": "@angular-devkit/build-angular:browser", + "executor": "@angular/build:application", "options": { - "outputPath": "dist/cdk-demo", - "tsConfig": "apps/cdk-demo/tsconfig.app.json", - "main": "apps/cdk-demo/main.ts", + "outputPath": { + "base": "dist/cdk-demo", + "browser": "" + }, "index": "apps/cdk-demo/index.html", + "browser": "apps/cdk-demo/main.ts", + "tsConfig": "apps/cdk-demo/tsconfig.app.json", + "inlineStyleLanguage": "scss", "assets": ["apps/cdk-demo/assets"], - "polyfills": "apps/cdk-demo/polyfills.ts", "styles": [".global/integration-app.style.scss", "apps/cdk-demo/styles.scss"], - "aot": false, - "vendorChunk": true, - "extractLicenses": false, - "buildOptimizer": false, - "sourceMap": true, - "optimization": false, - "namedChunks": true + "scripts": [], + "stylePreprocessorOptions": { + "includePaths": ["./src"], + "sass": { + "silenceDeprecations": ["import"] + } + } }, "configurations": { "production": { - "fileReplacements": [ + "budgets": [ { - "replace": "apps/cdk-demo/environments/environment.ts", - "with": "apps/cdk-demo/environments/environment.prod.ts" + "type": "initial", + "maximumWarning": "1mb", + "maximumError": "2mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" } ], - "optimization": true, "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "buildOptimizer": true - }, - "development": { "fileReplacements": [ { "replace": "apps/cdk-demo/environments/environment.ts", "with": "apps/cdk-demo/environments/environment.prod.ts" } - ], + ] + }, + "development": { "optimization": false, - "outputHashing": "all", - "aot": true + "extractLicenses": false, + "sourceMap": true } }, "defaultConfiguration": "production", - "outputs": ["{projectRoot}/dist/cdk-demo"] + "outputs": ["{options.outputPath}"] }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "buildTarget": "cdk-demo:build" - }, + "executor": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "cdk-demo:build:production" diff --git a/apps/cdk-demo/src/app-routing.module.ts b/apps/cdk-demo/src/app-routing.module.ts deleted file mode 100644 index cb7b97ae0..000000000 --- a/apps/cdk-demo/src/app-routing.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable */ -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -@NgModule({ - imports: [ - RouterModule.forRoot( - [ - { - path: '', - pathMatch: 'full', - redirectTo: 'samples', - }, - { - path: 'samples', - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'guide', - }, - { - path: 'guide', - loadChildren: () => - import('./samples/guide/guide.module').then( - (m) => m.GuideModule, - ), - }, - ], - }, - ], - {useHash: true, scrollPositionRestoration: 'enabled'}, - ), - ], - exports: [RouterModule], -}) -export class AppRoutingModule {} diff --git a/apps/cdk-demo/src/app.component.ts b/apps/cdk-demo/src/app.component.ts index cf11b2953..0ab16fb6a 100644 --- a/apps/cdk-demo/src/app.component.ts +++ b/apps/cdk-demo/src/app.component.ts @@ -1,7 +1,22 @@ import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MatDivider, MatList, MatListItem} from '@angular/material/list'; +import {MatDrawer, MatDrawerContainer, MatDrawerContent} from '@angular/material/sidenav'; +import {MatToolbar} from '@angular/material/toolbar'; +import {RouterLink, RouterOutlet} from '@angular/router'; @Component({ selector: 'app-root', + imports: [ + MatDivider, + MatDrawer, + MatDrawerContainer, + MatDrawerContent, + MatList, + MatListItem, + MatToolbar, + RouterLink, + RouterOutlet, + ], templateUrl: './app.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/apps/cdk-demo/src/app.config.ts b/apps/cdk-demo/src/app.config.ts new file mode 100644 index 000000000..a1e3fa4ae --- /dev/null +++ b/apps/cdk-demo/src/app.config.ts @@ -0,0 +1,39 @@ +import type {ApplicationConfig} from '@angular/core'; +import { + provideBrowserGlobalErrorListeners, + provideZonelessChangeDetection, +} from '@angular/core'; +import {provideAnimationsAsync} from '@angular/platform-browser/animations/async'; +import { + PreloadAllModules, + provideRouter, + withComponentInputBinding, + withHashLocation, + withInMemoryScrolling, + withPreloading, + withRouterConfig, +} from '@angular/router'; +import {provideAmountFormat} from '@angular-ru/cdk/directives'; + +import {routes} from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideZonelessChangeDetection(), + provideBrowserGlobalErrorListeners(), + provideRouter( + routes, + withPreloading(PreloadAllModules), + withRouterConfig({ + paramsInheritanceStrategy: 'always', + }), + withComponentInputBinding(), + withInMemoryScrolling({ + scrollPositionRestoration: 'enabled', + }), + withHashLocation(), + ), + provideAnimationsAsync(), + provideAmountFormat(), + ], +}; diff --git a/apps/cdk-demo/src/app.module.ts b/apps/cdk-demo/src/app.module.ts deleted file mode 100644 index c114d4375..000000000 --- a/apps/cdk-demo/src/app.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {HttpClientModule} from '@angular/common/http'; -import {NgModule} from '@angular/core'; -import {MatListModule} from '@angular/material/list'; -import {MatSidenavModule} from '@angular/material/sidenav'; -import {MatToolbarModule} from '@angular/material/toolbar'; -import {BrowserModule} from '@angular/platform-browser'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {AmountFormatModule} from '@angular-ru/cdk/directives'; - -import {AppComponent} from './app.component'; -import {AppRoutingModule} from './app-routing.module'; - -@NgModule({ - imports: [ - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - HttpClientModule, - MatListModule, - MatSidenavModule, - MatToolbarModule, - AmountFormatModule.forRoot(), - ], - declarations: [AppComponent], - bootstrap: [AppComponent], -}) -export class AppModule {} diff --git a/apps/cdk-demo/src/app.routes.ts b/apps/cdk-demo/src/app.routes.ts new file mode 100644 index 000000000..1e272965a --- /dev/null +++ b/apps/cdk-demo/src/app.routes.ts @@ -0,0 +1,31 @@ +import type {Routes} from '@angular/router'; +import {provideInputFilter} from '@angular-ru/cdk/directives'; + +import {REG_EXP_ONLY_NUMBERS} from './samples/guide/properties/constants'; + +export const routes: Routes = [ + { + path: '', + pathMatch: 'full', + redirectTo: 'samples', + }, + { + path: 'samples', + children: [ + { + path: '', + pathMatch: 'full', + redirectTo: 'guide', + }, + { + path: 'guide', + loadComponent: async () => import('./samples/guide/guide.component'), + providers: [ + provideInputFilter({ + default: REG_EXP_ONLY_NUMBERS, + }), + ], + }, + ], + }, +]; diff --git a/apps/cdk-demo/src/samples/guide/guide.component.ts b/apps/cdk-demo/src/samples/guide/guide.component.ts index 07c9caddc..e0310ad53 100644 --- a/apps/cdk-demo/src/samples/guide/guide.component.ts +++ b/apps/cdk-demo/src/samples/guide/guide.component.ts @@ -1,21 +1,31 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; -import {FormBuilder, FormControl, FormGroup} from '@angular/forms'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; +import {MatFormField, MatInput, MatLabel} from '@angular/material/input'; +import {AmountFormat, InputFilter} from '@angular-ru/cdk/directives'; import {REG_EXP_ONLY_NUMBERS} from './properties/constants'; @Component({ selector: 'guide', + imports: [ + AmountFormat, + InputFilter, + MatFormField, + MatInput, + MatLabel, + ReactiveFormsModule, + ], templateUrl: './guide.component.html', styleUrls: ['./guide.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class GuideComponent { +export default class GuideComponent { + private readonly fb = inject(FormBuilder); + public value = 'abc'; public valueNumbers = '123'; public filterRegExp = /[a-z]+/; public onlyNumbers: RegExp = REG_EXP_ONLY_NUMBERS; public amountForm: FormGroup = this.fb.group({sum: null}); public filterControl: FormControl = new FormControl(this.value); - - constructor(private readonly fb: FormBuilder) {} } diff --git a/apps/cdk-demo/src/samples/guide/guide.module.ts b/apps/cdk-demo/src/samples/guide/guide.module.ts deleted file mode 100644 index 398524fd1..000000000 --- a/apps/cdk-demo/src/samples/guide/guide.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {ScrollingModule} from '@angular/cdk/scrolling'; -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {ReactiveFormsModule} from '@angular/forms'; -import {MatIconModule} from '@angular/material/icon'; -import {MatInputModule} from '@angular/material/input'; -import {RouterModule} from '@angular/router'; -import {AmountFormatModule, InputFilterModule} from '@angular-ru/cdk/directives'; - -import {GuideComponent} from './guide.component'; -import {REG_EXP_ONLY_NUMBERS} from './properties/constants'; - -@NgModule({ - imports: [ - AmountFormatModule, - RouterModule.forChild([ - { - path: '', - component: GuideComponent, - }, - ]), - CommonModule, - MatIconModule, - MatInputModule, - ReactiveFormsModule, - ScrollingModule, - InputFilterModule.forChild({ - default: REG_EXP_ONLY_NUMBERS, - }), - ], - declarations: [GuideComponent], -}) -export class GuideModule {} diff --git a/apps/cdk-demo/tsconfig.app.json b/apps/cdk-demo/tsconfig.app.json index 5ede5f85d..64c8041c9 100644 --- a/apps/cdk-demo/tsconfig.app.json +++ b/apps/cdk-demo/tsconfig.app.json @@ -1,4 +1,10 @@ { "extends": "../../tsconfig.json", - "include": ["main.ts", "polyfills.ts"] + "compilerOptions": { + "types": [] + }, + "include": ["main.ts"], + "angularCompilerOptions": { + "compilationMode": "full" + } } diff --git a/apps/excel-demo/main.ts b/apps/excel-demo/main.ts index bcc97cfbe..c4476c284 100644 --- a/apps/excel-demo/main.ts +++ b/apps/excel-demo/main.ts @@ -1,13 +1,8 @@ -import {enableProdMode} from '@angular/core'; -import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {bootstrapApplication} from '@angular/platform-browser'; -import {environment} from './environments/environment'; -import {AppModule} from './src/app.module'; +import {AppComponent} from './src/app.component'; +import {appConfig} from './src/app.config'; -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic() - .bootstrapModule(AppModule) - .catch((error: Error): void => console.error(error)); +bootstrapApplication(AppComponent, appConfig).catch((error: unknown): void => + console.error(error), +); diff --git a/apps/excel-demo/polyfills.ts b/apps/excel-demo/polyfills.ts deleted file mode 100644 index aa09a9ff6..000000000 --- a/apps/excel-demo/polyfills.ts +++ /dev/null @@ -1 +0,0 @@ -import 'zone.js'; diff --git a/apps/excel-demo/project.json b/apps/excel-demo/project.json index 9ad8adf99..187dbd991 100644 --- a/apps/excel-demo/project.json +++ b/apps/excel-demo/project.json @@ -6,58 +6,58 @@ "tags": ["apps"], "targets": { "build": { - "executor": "@angular-devkit/build-angular:browser", + "executor": "@angular/build:application", "options": { - "outputPath": "dist/excel-demo", - "tsConfig": "apps/excel-demo/tsconfig.app.json", - "main": "apps/excel-demo/main.ts", + "outputPath": { + "base": "dist/excel-demo", + "browser": "" + }, "index": "apps/excel-demo/index.html", + "browser": "apps/excel-demo/main.ts", + "tsConfig": "apps/excel-demo/tsconfig.app.json", + "inlineStyleLanguage": "scss", "assets": ["apps/excel-demo/assets"], - "polyfills": "apps/excel-demo/polyfills.ts", "styles": [".global/integration-app.style.scss", "apps/excel-demo/styles.css"], - "aot": false, - "vendorChunk": true, - "extractLicenses": false, - "buildOptimizer": false, - "sourceMap": true, - "optimization": false, - "namedChunks": true + "scripts": [], + "stylePreprocessorOptions": { + "includePaths": ["./src"], + "sass": { + "silenceDeprecations": ["import"] + } + } }, "configurations": { "production": { - "fileReplacements": [ + "budgets": [ { - "replace": "apps/excel-demo/environments/environment.ts", - "with": "apps/excel-demo/environments/environment.prod.ts" + "type": "initial", + "maximumWarning": "1mb", + "maximumError": "2mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" } ], - "optimization": true, "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "buildOptimizer": true - }, - "development": { "fileReplacements": [ { "replace": "apps/excel-demo/environments/environment.ts", "with": "apps/excel-demo/environments/environment.prod.ts" } - ], + ] + }, + "development": { "optimization": false, - "aot": true + "extractLicenses": false, + "sourceMap": true } }, "defaultConfiguration": "production", - "outputs": ["{projectRoot}/dist/excel-demo"] + "outputs": ["{options.outputPath}"] }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "buildTarget": "excel-demo:build" - }, + "executor": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "excel-demo:build:production" diff --git a/apps/excel-demo/src/app.component.html b/apps/excel-demo/src/app.component.html index 777af6017..74c3b2fa9 100644 --- a/apps/excel-demo/src/app.component.html +++ b/apps/excel-demo/src/app.component.html @@ -36,6 +36,7 @@

Export excel

- + diff --git a/apps/ngxs-demo/src/article/dialog/article-dialog.component.ts b/apps/ngxs-demo/src/article/dialog/article-dialog.component.ts index f2379276e..4f17892f9 100644 --- a/apps/ngxs-demo/src/article/dialog/article-dialog.component.ts +++ b/apps/ngxs-demo/src/article/dialog/article-dialog.component.ts @@ -1,22 +1,60 @@ -import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import { + FormBuilder, + FormGroup, + FormsModule, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import {MatButton} from '@angular/material/button'; +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogContent, + MatDialogRef, + MatDialogTitle, +} from '@angular/material/dialog'; +import {MatFormField, MatInput, MatLabel} from '@angular/material/input'; import {Article} from '../article'; @Component({ selector: 'article-dialog', + imports: [ + FormsModule, + MatButton, + MatDialogActions, + MatDialogContent, + MatDialogTitle, + MatFormField, + MatInput, + MatLabel, + ReactiveFormsModule, + ], templateUrl: './article-dialog.component.html', + styles: ` + .content { + display: flex; + flex-direction: column; + gap: 1rem; + } + + mat-form-field { + padding-top: 0.25rem; + } + `, changeDetection: ChangeDetectionStrategy.OnPush, }) export class ArticleDialogComponent { + public dialogRef = inject>(MatDialogRef); + public data = inject
(MAT_DIALOG_DATA); + private readonly fb = inject(FormBuilder); + public form: FormGroup; - constructor( - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: Article, - private readonly fb: FormBuilder, - ) { + constructor() { + const data = this.data; + this.form = this.fb.group({ uid: this.fb.control(data.uid, [Validators.required]), title: this.fb.control(data.title, [Validators.required]), diff --git a/apps/ngxs-demo/src/count/count-sub.state.ts b/apps/ngxs-demo/src/count/count-sub.state.ts index 577790b5b..be7dfd70a 100644 --- a/apps/ngxs-demo/src/count/count-sub.state.ts +++ b/apps/ngxs-demo/src/count/count-sub.state.ts @@ -25,7 +25,9 @@ import {CountModel} from './count-model'; export class CountSubState extends NgxsImmutableDataRepository { @Debounce() @DataAction() - public setDebounceSubValue(@Payload('value') @Named('val') value: number): void { - this.ctx.setState({val: value}); + public setDebounceSubValue( + @Payload('value') @Named('val') value: number | string, + ): void { + this.ctx.setState({val: parseFloat(value as string) || 0}); } } diff --git a/apps/ngxs-demo/src/count/count.component.html b/apps/ngxs-demo/src/count/count.component.html index a60d1a357..deaf4ee24 100644 --- a/apps/ngxs-demo/src/count/count.component.html +++ b/apps/ngxs-demo/src/count/count.component.html @@ -11,7 +11,7 @@

CountState

-PS: CountSubState will be persistence in sessionStorage +PS: CountSubState will be persisted in sessionStorage

@@ -29,10 +29,30 @@

CountState


Actions: - - - - + + + +

diff --git a/apps/ngxs-demo/src/count/count.component.ts b/apps/ngxs-demo/src/count/count.component.ts index 42a322390..3611b8da6 100644 --- a/apps/ngxs-demo/src/count/count.component.ts +++ b/apps/ngxs-demo/src/count/count.component.ts @@ -1,16 +1,17 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {AsyncPipe, JsonPipe} from '@angular/common'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {FormsModule} from '@angular/forms'; import {CountState} from './count.state'; import {CountSubState} from './count-sub.state'; @Component({ selector: 'count', + imports: [AsyncPipe, FormsModule, JsonPipe], templateUrl: './count.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class CountComponent { - constructor( - public counter: CountState, - public subCount: CountSubState, - ) {} + public counter = inject(CountState); + public subCount = inject(CountSubState); } diff --git a/apps/ngxs-demo/src/count/count.module.ts b/apps/ngxs-demo/src/count/count.module.ts deleted file mode 100644 index 6fef8880a..000000000 --- a/apps/ngxs-demo/src/count/count.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {NgxsModule} from '@ngxs/store'; - -import {CountComponent} from './count.component'; -import {CountState} from './count.state'; -import {CountSubState} from './count-sub.state'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - NgxsModule.forFeature([CountState, CountSubState]), - RouterModule.forChild([{path: '', component: CountComponent}]), - ], - declarations: [CountComponent], -}) -export class CountModule {} diff --git a/apps/ngxs-demo/src/count/count.routes.ts b/apps/ngxs-demo/src/count/count.routes.ts new file mode 100644 index 000000000..3ee0d4f5d --- /dev/null +++ b/apps/ngxs-demo/src/count/count.routes.ts @@ -0,0 +1,16 @@ +import type {Routes} from '@angular/router'; +import {provideStates} from '@ngxs/store'; + +import {CountComponent} from './count.component'; +import {CountState} from './count.state'; +import {CountSubState} from './count-sub.state'; + +const routes: Routes = [ + { + path: '', + component: CountComponent, + providers: [provideStates([CountState, CountSubState])], + }, +]; + +export default routes; diff --git a/apps/ngxs-demo/src/person/person.component.html b/apps/ngxs-demo/src/person/person.component.html index 1d2d82dec..c570fda42 100644 --- a/apps/ngxs-demo/src/person/person.component.html +++ b/apps/ngxs-demo/src/person/person.component.html @@ -1,5 +1,5 @@ Person state -
-

{{ person.title }}

-

{{ person.description }}

-
+@if (person.state$ | async; as person) { +
+

{{ person.title }}

+

{{ person.description }}

+
+} diff --git a/apps/ngxs-demo/src/person/person.component.ts b/apps/ngxs-demo/src/person/person.component.ts index 55ea91bbf..7e2db376d 100644 --- a/apps/ngxs-demo/src/person/person.component.ts +++ b/apps/ngxs-demo/src/person/person.component.ts @@ -1,12 +1,14 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {AsyncPipe} from '@angular/common'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {PersonState} from './person.state'; @Component({ selector: 'person', + imports: [AsyncPipe], templateUrl: './person.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class PersonComponent { - constructor(public person: PersonState) {} + public person = inject(PersonState); } diff --git a/apps/ngxs-demo/src/person/person.module.ts b/apps/ngxs-demo/src/person/person.module.ts deleted file mode 100644 index 6d5c7653e..000000000 --- a/apps/ngxs-demo/src/person/person.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {HttpClientModule} from '@angular/common/http'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; -import {NgxsModule} from '@ngxs/store'; - -import {PersonComponent} from './person.component'; -import {PersonResolver} from './person.resolver'; -import {PersonService} from './person.service'; -import {PersonState} from './person.state'; - -@NgModule({ - imports: [ - CommonModule, - HttpClientModule, - NgxsModule.forFeature([PersonState]), - RouterModule.forChild([ - { - path: '', - component: PersonComponent, - resolve: { - content: PersonResolver, - }, - }, - ]), - ], - declarations: [PersonComponent], - providers: [PersonResolver, PersonService], -}) -export class PersonModule {} diff --git a/apps/ngxs-demo/src/person/person.resolver.ts b/apps/ngxs-demo/src/person/person.resolver.ts index cfa1533df..f72cf14ab 100644 --- a/apps/ngxs-demo/src/person/person.resolver.ts +++ b/apps/ngxs-demo/src/person/person.resolver.ts @@ -1,15 +1,8 @@ -import {Injectable} from '@angular/core'; -import {Resolve} from '@angular/router'; -import {Observable} from 'rxjs'; +import {inject} from '@angular/core'; +import type {ResolveFn} from '@angular/router'; import {PersonState} from './person.state'; -import {PersonModel} from './person-model'; +import type {PersonModel} from './person-model'; -@Injectable() -export class PersonResolver implements Resolve { - constructor(private readonly personState: PersonState) {} - - public resolve(): Observable { - return this.personState.getContent(); - } -} +export const personResolver: ResolveFn = () => + inject(PersonState).getContent(); diff --git a/apps/ngxs-demo/src/person/person.routes.ts b/apps/ngxs-demo/src/person/person.routes.ts new file mode 100644 index 000000000..b9877a684 --- /dev/null +++ b/apps/ngxs-demo/src/person/person.routes.ts @@ -0,0 +1,17 @@ +import type {Routes} from '@angular/router'; +import {provideStates} from '@ngxs/store'; + +import {PersonComponent} from './person.component'; +import {personResolver} from './person.resolver'; +import {PersonState} from './person.state'; + +const routes: Routes = [ + { + path: '', + component: PersonComponent, + providers: [provideStates([PersonState])], + resolve: {content: personResolver}, + }, +]; + +export default routes; diff --git a/apps/ngxs-demo/src/person/person.service.ts b/apps/ngxs-demo/src/person/person.service.ts index 09af96841..e907ba563 100644 --- a/apps/ngxs-demo/src/person/person.service.ts +++ b/apps/ngxs-demo/src/person/person.service.ts @@ -1,13 +1,15 @@ import {HttpClient} from '@angular/common/http'; -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {PersonModel} from './person-model'; -@Injectable() +@Injectable({ + providedIn: 'root', +}) export class PersonService { - constructor(private readonly httpService: HttpClient) {} + private readonly httpService = inject(HttpClient); public fetchAll(): Observable { return this.httpService diff --git a/apps/ngxs-demo/src/person/person.state.ts b/apps/ngxs-demo/src/person/person.state.ts index 6b3b3128c..e8af3f491 100644 --- a/apps/ngxs-demo/src/person/person.state.ts +++ b/apps/ngxs-demo/src/person/person.state.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {StateRepository} from '@angular-ru/ngxs/decorators'; import {NgxsImmutableDataRepository} from '@angular-ru/ngxs/repositories'; import {State} from '@ngxs/store'; @@ -15,9 +15,7 @@ import {PersonModel} from './person-model'; }) @Injectable() export class PersonState extends NgxsImmutableDataRepository { - constructor(private readonly personService: PersonService) { - super(); - } + private readonly personService = inject(PersonService); public getContent(): Observable { return this.personService diff --git a/apps/ngxs-demo/src/todo/todo.component.html b/apps/ngxs-demo/src/todo/todo.component.html index 0bcf98248..058bec411 100644 --- a/apps/ngxs-demo/src/todo/todo.component.html +++ b/apps/ngxs-demo/src/todo/todo.component.html @@ -1,5 +1,5 @@
TodoState -PS: TodoState will be persistence in localStorage (ttl: 30sec) +PS: TodoState will be persisted in localStorage (ttl: 30sec)
- - + +
    -
  • - {{ todoItem }} - -
  • + @for (todoItem of todo.state$ | async; track todoItem; let i = $index) { +
  • + {{ todoItem }} + +
  • + }
diff --git a/apps/ngxs-demo/src/todo/todo.component.ts b/apps/ngxs-demo/src/todo/todo.component.ts index 9a29c60d6..3d892145a 100644 --- a/apps/ngxs-demo/src/todo/todo.component.ts +++ b/apps/ngxs-demo/src/todo/todo.component.ts @@ -1,12 +1,14 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {AsyncPipe} from '@angular/common'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {TodoState} from './todo.state'; @Component({ selector: 'todo', + imports: [AsyncPipe], templateUrl: './todo.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class TodoComponent { - constructor(public todo: TodoState) {} + public todo = inject(TodoState); } diff --git a/apps/ngxs-demo/src/todo/todo.module.ts b/apps/ngxs-demo/src/todo/todo.module.ts deleted file mode 100644 index 959890a21..000000000 --- a/apps/ngxs-demo/src/todo/todo.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {MatSnackBarModule} from '@angular/material/snack-bar'; -import {RouterModule} from '@angular/router'; -import {NgxsModule} from '@ngxs/store'; - -import {TodoComponent} from './todo.component'; -import {TodoState} from './todo.state'; - -@NgModule({ - imports: [ - CommonModule, - MatSnackBarModule, - NgxsModule.forFeature([TodoState]), - RouterModule.forChild([{path: '', component: TodoComponent}]), - ], - declarations: [TodoComponent], -}) -export class TodoModule {} diff --git a/apps/ngxs-demo/src/todo/todo.routes.ts b/apps/ngxs-demo/src/todo/todo.routes.ts new file mode 100644 index 000000000..de7b82948 --- /dev/null +++ b/apps/ngxs-demo/src/todo/todo.routes.ts @@ -0,0 +1,15 @@ +import type {Routes} from '@angular/router'; +import {provideStates} from '@ngxs/store'; + +import {TodoComponent} from './todo.component'; +import {TodoState} from './todo.state'; + +const routes: Routes = [ + { + path: '', + component: TodoComponent, + providers: [provideStates([TodoState])], + }, +]; + +export default routes; diff --git a/apps/ngxs-demo/src/todo/todo.state.ts b/apps/ngxs-demo/src/todo/todo.state.ts index 9a8ff5d5e..4dee234fa 100644 --- a/apps/ngxs-demo/src/todo/todo.state.ts +++ b/apps/ngxs-demo/src/todo/todo.state.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {MatSnackBar} from '@angular/material/snack-bar'; import {Immutable} from '@angular-ru/cdk/typings'; import { @@ -34,11 +34,9 @@ export class TodoState extends NgxsImmutableDataRepository implements NgxsDataAfterExpired, NgxsDataAfterStorageEvent { - public expired$ = new Subject(); + private readonly snackBar = inject(MatSnackBar); - constructor(private readonly snackBar: MatSnackBar) { - super(); - } + public expired$ = new Subject(); @DataAction() public addTodo(@Payload('todo') todo: string): void { diff --git a/apps/ngxs-demo/src/user/user.component.html b/apps/ngxs-demo/src/user/user.component.html index c6cd5ccc5..704edb5ad 100644 --- a/apps/ngxs-demo/src/user/user.component.html +++ b/apps/ngxs-demo/src/user/user.component.html @@ -1,8 +1,12 @@

User state

-
-

{{ user.name }}

-

{{ user.email }}

-
+@if (user$ | async; as user) { +
+

{{ user.name }}

+

{{ user.email }}

+
+} -
LOADING...
+@if ((isLoading$ | async) === true) { +
LOADING...
+} diff --git a/apps/ngxs-demo/src/user/user.component.ts b/apps/ngxs-demo/src/user/user.component.ts index 5a91f2869..648a63aea 100644 --- a/apps/ngxs-demo/src/user/user.component.ts +++ b/apps/ngxs-demo/src/user/user.component.ts @@ -1,23 +1,24 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; -import {Select} from '@ngxs/store'; -import {Observable} from 'rxjs'; +import {AsyncPipe} from '@angular/common'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {Store} from '@ngxs/store'; import {UserState} from './user.state'; -import {UserModel} from './user-model'; @Component({ selector: 'user', + imports: [AsyncPipe], templateUrl: './user.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class UserComponent { - @Select(UserState.getEntity) - public user$!: Observable; + public userState = inject(UserState); - @Select(UserState.isLoading) - public isLoading$!: Observable; + private readonly store = inject(Store); - constructor(public userState: UserState) { + protected user$ = this.store.select(UserState.getEntity); + protected isLoading$ = this.store.select(UserState.isLoading); + + constructor() { // eslint-disable-next-line rxjs/no-ignored-observable this.userState.loadUser(); } diff --git a/apps/ngxs-demo/src/user/user.module.ts b/apps/ngxs-demo/src/user/user.module.ts deleted file mode 100644 index 0cc0a6fd5..000000000 --- a/apps/ngxs-demo/src/user/user.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {HttpClientModule} from '@angular/common/http'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; -import {NgxsModule} from '@ngxs/store'; - -import {UserComponent} from './user.component'; -import {UserService} from './user.service'; -import {UserState} from './user.state'; - -@NgModule({ - imports: [ - CommonModule, - HttpClientModule, - NgxsModule.forFeature([UserState]), - RouterModule.forChild([ - { - path: '', - component: UserComponent, - }, - ]), - ], - declarations: [UserComponent], - providers: [UserService], -}) -export class UserModule {} diff --git a/apps/ngxs-demo/src/user/user.routes.ts b/apps/ngxs-demo/src/user/user.routes.ts new file mode 100644 index 000000000..ae8fa8f64 --- /dev/null +++ b/apps/ngxs-demo/src/user/user.routes.ts @@ -0,0 +1,15 @@ +import type {Routes} from '@angular/router'; +import {provideStates} from '@ngxs/store'; + +import {UserComponent} from './user.component'; +import {UserState} from './user.state'; + +const routes: Routes = [ + { + path: '', + component: UserComponent, + providers: [provideStates([UserState])], + }, +]; + +export default routes; diff --git a/apps/ngxs-demo/src/user/user.service.ts b/apps/ngxs-demo/src/user/user.service.ts index c7ad835a7..06c8e3c2d 100644 --- a/apps/ngxs-demo/src/user/user.service.ts +++ b/apps/ngxs-demo/src/user/user.service.ts @@ -1,14 +1,17 @@ import {HttpClient} from '@angular/common/http'; -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {Observable} from 'rxjs'; import {delay, map} from 'rxjs/operators'; import {UserModel} from './user-model'; -@Injectable() +@Injectable({ + providedIn: 'root', +}) export class UserService { + private readonly httpService = inject(HttpClient); + private readonly SIMULATE_REQUEST_DELAY: number = 2000; - constructor(private readonly httpService: HttpClient) {} public loadUser(): Observable { return this.httpService.get<{data: UserModel}>('/assets/user.json').pipe( diff --git a/apps/ngxs-demo/src/user/user.state.ts b/apps/ngxs-demo/src/user/user.state.ts index b74c85e4e..72baeea5e 100644 --- a/apps/ngxs-demo/src/user/user.state.ts +++ b/apps/ngxs-demo/src/user/user.state.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {DataAction, StateRepository} from '@angular-ru/ngxs/decorators'; import {NgxsImmutableDataRepository} from '@angular-ru/ngxs/repositories'; import {Selector, State} from '@ngxs/store'; @@ -26,9 +26,7 @@ interface UserStateModel { }) @Injectable() export class UserState extends NgxsImmutableDataRepository { - constructor(private readonly userService: UserService) { - super(); - } + private readonly userService = inject(UserService); @Selector() public static getEntity(state: UserStateModel): UserModel { @@ -40,7 +38,7 @@ export class UserState extends NgxsImmutableDataRepository { return state.loading; } - // Note: Also can be configured globally by providing custom NGXS_DATA_CONFIG + // Note: Also can be configured globally using provideNgxsDataPlugin({subscribeRequired: false}) @DataAction({subscribeRequired: false}) public loadUser(): Observable { this.ctx.patchState({loading: true}); diff --git a/apps/ngxs-demo/styles.scss b/apps/ngxs-demo/styles.scss index 519966e63..e3b9688db 100644 --- a/apps/ngxs-demo/styles.scss +++ b/apps/ngxs-demo/styles.scss @@ -62,7 +62,7 @@ button { position: relative; display: flex; background: #fff; - font-size: 20px; + font-size: 16px; margin: auto; min-height: 40vh; border: 1px solid rgba(0, 0, 0, 0.2); diff --git a/apps/ngxs-demo/tsconfig.app.json b/apps/ngxs-demo/tsconfig.app.json index 5ede5f85d..64c8041c9 100644 --- a/apps/ngxs-demo/tsconfig.app.json +++ b/apps/ngxs-demo/tsconfig.app.json @@ -1,4 +1,10 @@ { "extends": "../../tsconfig.json", - "include": ["main.ts", "polyfills.ts"] + "compilerOptions": { + "types": [] + }, + "include": ["main.ts"], + "angularCompilerOptions": { + "compilationMode": "full" + } } diff --git a/apps/tooltip-demo/main.ts b/apps/tooltip-demo/main.ts index 7f04f8cab..bd35bd4f6 100644 --- a/apps/tooltip-demo/main.ts +++ b/apps/tooltip-demo/main.ts @@ -1,13 +1,8 @@ -import {enableProdMode} from '@angular/core'; -import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {bootstrapApplication} from '@angular/platform-browser'; -import {environment} from './environments/environment'; -import {AppModule} from './src/app.module'; +import {AppComponent} from './src/app.component'; +import {appConfig} from './src/app.config'; -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic() - .bootstrapModule(AppModule, {ngZoneEventCoalescing: true}) - .catch((error: Error): void => console.error(error)); +bootstrapApplication(AppComponent, appConfig).catch((error: unknown) => + console.error(error), +); diff --git a/apps/tooltip-demo/polyfills.ts b/apps/tooltip-demo/polyfills.ts deleted file mode 100644 index 59e6a6b7f..000000000 --- a/apps/tooltip-demo/polyfills.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '@angular-ru/cdk/zone.js/unpatch-zone'; -import 'zone.js'; diff --git a/apps/tooltip-demo/project.json b/apps/tooltip-demo/project.json index bd69b26f1..35ff4bf03 100644 --- a/apps/tooltip-demo/project.json +++ b/apps/tooltip-demo/project.json @@ -6,63 +6,64 @@ "tags": ["apps"], "targets": { "build": { - "executor": "@angular-devkit/build-angular:browser", + "executor": "@angular/build:application", "options": { - "outputPath": "dist/tooltip-demo", - "tsConfig": "apps/tooltip-demo/tsconfig.app.json", - "main": "apps/tooltip-demo/main.ts", + "outputPath": { + "base": "dist/tooltip-demo", + "browser": "" + }, "index": "apps/tooltip-demo/index.html", + "browser": "apps/tooltip-demo/main.ts", + "tsConfig": "apps/tooltip-demo/tsconfig.app.json", + "inlineStyleLanguage": "scss", "assets": ["apps/tooltip-demo/assets"], - "polyfills": "apps/tooltip-demo/polyfills.ts", "styles": [".global/integration-app.style.scss", "apps/tooltip-demo/styles.scss"], - "aot": false, - "vendorChunk": true, - "extractLicenses": false, - "buildOptimizer": false, - "sourceMap": true, - "optimization": false, - "namedChunks": true + "scripts": [], + "stylePreprocessorOptions": { + "includePaths": ["./src"], + "sass": { + "silenceDeprecations": ["import"] + } + } }, "configurations": { "production": { - "fileReplacements": [ + "budgets": [ { - "replace": "apps/tooltip-demo/environments/environment.ts", - "with": "apps/tooltip-demo/environments/environment.prod.ts" + "type": "initial", + "maximumWarning": "1mb", + "maximumError": "2mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" } ], - "optimization": true, "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "buildOptimizer": true - }, - "development": { "fileReplacements": [ { - "replace": "apps/flex-layout-demo/environments/environment.ts", - "with": "apps/flex-layout-demo/environments/environment.prod.ts" + "replace": "apps/tooltip-demo/environments/environment.ts", + "with": "apps/tooltip-demo/environments/environment.prod.ts" } - ], - "optimization": false + ] + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true } }, "defaultConfiguration": "production", - "outputs": ["{projectRoot}/dist/tooltip-demo"] + "outputs": ["{options.outputPath}"] }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "buildTarget": "tooltip-demo:build" - }, + "executor": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "tooltip-demo:build:production" }, "development": { - "buildTarget": "development-demo:build:production" + "buildTarget": "tooltip-demo:build:development" } }, "defaultConfiguration": "development" diff --git a/apps/tooltip-demo/src/app-routing.module.ts b/apps/tooltip-demo/src/app-routing.module.ts deleted file mode 100644 index cb7b97ae0..000000000 --- a/apps/tooltip-demo/src/app-routing.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable */ -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -@NgModule({ - imports: [ - RouterModule.forRoot( - [ - { - path: '', - pathMatch: 'full', - redirectTo: 'samples', - }, - { - path: 'samples', - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'guide', - }, - { - path: 'guide', - loadChildren: () => - import('./samples/guide/guide.module').then( - (m) => m.GuideModule, - ), - }, - ], - }, - ], - {useHash: true, scrollPositionRestoration: 'enabled'}, - ), - ], - exports: [RouterModule], -}) -export class AppRoutingModule {} diff --git a/apps/tooltip-demo/src/app.component.ts b/apps/tooltip-demo/src/app.component.ts index cf11b2953..0ab16fb6a 100644 --- a/apps/tooltip-demo/src/app.component.ts +++ b/apps/tooltip-demo/src/app.component.ts @@ -1,7 +1,22 @@ import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MatDivider, MatList, MatListItem} from '@angular/material/list'; +import {MatDrawer, MatDrawerContainer, MatDrawerContent} from '@angular/material/sidenav'; +import {MatToolbar} from '@angular/material/toolbar'; +import {RouterLink, RouterOutlet} from '@angular/router'; @Component({ selector: 'app-root', + imports: [ + MatDivider, + MatDrawer, + MatDrawerContainer, + MatDrawerContent, + MatList, + MatListItem, + MatToolbar, + RouterLink, + RouterOutlet, + ], templateUrl: './app.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/apps/tooltip-demo/src/app.config.ts b/apps/tooltip-demo/src/app.config.ts new file mode 100644 index 000000000..690d8578b --- /dev/null +++ b/apps/tooltip-demo/src/app.config.ts @@ -0,0 +1,41 @@ +import {provideHttpClient} from '@angular/common/http'; +import type {ApplicationConfig} from '@angular/core'; +import { + provideBrowserGlobalErrorListeners, + provideZonelessChangeDetection, +} from '@angular/core'; +import {provideAnimationsAsync} from '@angular/platform-browser/animations/async'; +import { + PreloadAllModules, + provideRouter, + withComponentInputBinding, + withHashLocation, + withInMemoryScrolling, + withPreloading, + withRouterConfig, +} from '@angular/router'; +import {provideTooltip} from '@angular-ru/cdk/tooltip'; + +import {routes} from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideZonelessChangeDetection(), + provideBrowserGlobalErrorListeners(), + provideRouter( + routes, + withPreloading(PreloadAllModules), + withRouterConfig({ + paramsInheritanceStrategy: 'always', + }), + withComponentInputBinding(), + withInMemoryScrolling({ + scrollPositionRestoration: 'enabled', + }), + withHashLocation(), + ), + provideHttpClient(), + provideAnimationsAsync(), + provideTooltip(), + ], +}; diff --git a/apps/tooltip-demo/src/app.module.ts b/apps/tooltip-demo/src/app.module.ts deleted file mode 100644 index bf8ce82b5..000000000 --- a/apps/tooltip-demo/src/app.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {HttpClientModule} from '@angular/common/http'; -import {NgModule} from '@angular/core'; -import {MatListModule} from '@angular/material/list'; -import {MatSidenavModule} from '@angular/material/sidenav'; -import {MatToolbarModule} from '@angular/material/toolbar'; -import {BrowserModule} from '@angular/platform-browser'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {TooltipModule} from '@angular-ru/cdk/tooltip'; - -import {AppComponent} from './app.component'; -import {AppRoutingModule} from './app-routing.module'; - -@NgModule({ - imports: [ - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - HttpClientModule, - MatListModule, - MatSidenavModule, - MatToolbarModule, - TooltipModule.forRoot(), - ], - declarations: [AppComponent], - bootstrap: [AppComponent], -}) -export class AppModule {} diff --git a/apps/tooltip-demo/src/app.routes.ts b/apps/tooltip-demo/src/app.routes.ts new file mode 100644 index 000000000..ea3cdb355 --- /dev/null +++ b/apps/tooltip-demo/src/app.routes.ts @@ -0,0 +1,23 @@ +import type {Routes} from '@angular/router'; + +export const routes: Routes = [ + { + path: '', + pathMatch: 'full', + redirectTo: 'samples', + }, + { + path: 'samples', + children: [ + { + path: '', + pathMatch: 'full', + redirectTo: 'guide', + }, + { + path: 'guide', + loadComponent: async () => import('./samples/guide/guide.component'), + }, + ], + }, +]; diff --git a/apps/tooltip-demo/src/samples/guide/guide.component.html b/apps/tooltip-demo/src/samples/guide/guide.component.html index 21f25c794..a6be1250d 100644 --- a/apps/tooltip-demo/src/samples/guide/guide.component.html +++ b/apps/tooltip-demo/src/samples/guide/guide.component.html @@ -76,7 +76,7 @@

With ng-template

> Left with HTML - ... +

Left html content ➡️

Example with remove Nodes [itemSize]="24" [style.height.px]="200" > - + ({ - id: i + 1, - title: Math.random() - // eslint-disable-next-line @typescript-eslint/no-magic-numbers - .toString(36) - .replaceAll(/[^a-z]+/g, '') - .slice(0, 5), - isMarked: false, - }), + public favorites = signal( + new Array(10000).fill(0).map( + // eslint-disable-next-line @typescript-eslint/typedef + (_, i: number): Favorite => ({ + id: i + 1, + title: Math.random() + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + .toString(36) + .replaceAll(/[^a-z]+/g, '') + .slice(0, 5), + isMarked: false, + }), + ), ); - constructor( - private readonly zone: NgZone, - protected readonly cd: ChangeDetectorRef, - ) {} - public markFavorite(favorite: Favorite): void { this.zone.runOutsideAngular((): void => { // eslint-disable-next-line no-restricted-globals setTimeout((): void => { - this.favorites = this.favorites.map((item: Favorite): Favorite => { - if (favorite.id === item.id) { - return {...favorite, isMarked: !favorite.isMarked}; - } + this.favorites.set( + this.favorites().map((item: Favorite): Favorite => { + if (favorite.id === item.id) { + return {...favorite, isMarked: !favorite.isMarked}; + } - return item; - }); - this.cd.detectChanges(); - // eslint-disable-next-line @typescript-eslint/no-magic-numbers + return item; + }), + ); }, 100); }); } @@ -60,11 +76,11 @@ export class GuideComponent { this.zone.runOutsideAngular((): void => { // eslint-disable-next-line no-restricted-globals setTimeout((): void => { - this.favorites = this.favorites.filter( - (item: Favorite): boolean => item.id !== favorite.id, + this.favorites.set( + this.favorites().filter( + (item: Favorite): boolean => item.id !== favorite.id, + ), ); - this.cd.detectChanges(); - // eslint-disable-next-line @typescript-eslint/no-magic-numbers }, 100); }); } diff --git a/apps/tooltip-demo/src/samples/guide/guide.module.ts b/apps/tooltip-demo/src/samples/guide/guide.module.ts deleted file mode 100644 index b486c51f1..000000000 --- a/apps/tooltip-demo/src/samples/guide/guide.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ScrollingModule} from '@angular/cdk/scrolling'; -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {MatIconModule} from '@angular/material/icon'; -import {RouterModule} from '@angular/router'; -import {TooltipModule} from '@angular-ru/cdk/tooltip'; - -import {GuideComponent} from './guide.component'; - -@NgModule({ - imports: [ - CommonModule, - MatIconModule, - RouterModule.forChild([ - { - path: '', - component: GuideComponent, - }, - ]), - ScrollingModule, - TooltipModule, - ], - declarations: [GuideComponent], -}) -export class GuideModule {} diff --git a/apps/tooltip-demo/styles.scss b/apps/tooltip-demo/styles.scss index b0201f17f..333f9cb65 100644 --- a/apps/tooltip-demo/styles.scss +++ b/apps/tooltip-demo/styles.scss @@ -1,5 +1,15 @@ @import './../../libs/cdk/tooltip/styles/index'; +html, +body { + height: 100%; +} + +body { + overflow: hidden; + margin: 0; +} + //noinspection DuplicatedCode .wrapper { display: grid; diff --git a/apps/tooltip-demo/tsconfig.app.json b/apps/tooltip-demo/tsconfig.app.json index 5ede5f85d..64c8041c9 100644 --- a/apps/tooltip-demo/tsconfig.app.json +++ b/apps/tooltip-demo/tsconfig.app.json @@ -1,4 +1,10 @@ { "extends": "../../tsconfig.json", - "include": ["main.ts", "polyfills.ts"] + "compilerOptions": { + "types": [] + }, + "include": ["main.ts"], + "angularCompilerOptions": { + "compilationMode": "full" + } } diff --git a/apps/virtual-table-demo/assets/img/excel.png b/apps/virtual-table-demo/assets/img/excel.png new file mode 100644 index 000000000..329e02161 Binary files /dev/null and b/apps/virtual-table-demo/assets/img/excel.png differ diff --git a/apps/virtual-table-demo/index.html b/apps/virtual-table-demo/index.html index 86c233412..875c75642 100644 --- a/apps/virtual-table-demo/index.html +++ b/apps/virtual-table-demo/index.html @@ -14,6 +14,7 @@ type="image/x-icon" /> + console.error(error)); +bootstrapApplication(AppComponent, appConfig).catch((error: unknown) => + console.error(error), +); diff --git a/apps/virtual-table-demo/polyfills.ts b/apps/virtual-table-demo/polyfills.ts deleted file mode 100644 index 59e6a6b7f..000000000 --- a/apps/virtual-table-demo/polyfills.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '@angular-ru/cdk/zone.js/unpatch-zone'; -import 'zone.js'; diff --git a/apps/virtual-table-demo/project.json b/apps/virtual-table-demo/project.json index 8853546bd..033afb4f1 100644 --- a/apps/virtual-table-demo/project.json +++ b/apps/virtual-table-demo/project.json @@ -6,61 +6,62 @@ "tags": ["apps"], "targets": { "build": { - "executor": "@angular-devkit/build-angular:browser", + "executor": "@angular/build:application", "options": { - "outputPath": "dist/virtual-table-demo", - "tsConfig": "apps/virtual-table-demo/tsconfig.app.json", - "main": "apps/virtual-table-demo/main.ts", + "outputPath": { + "base": "dist/virtual-table-demo", + "browser": "" + }, "index": "apps/virtual-table-demo/index.html", + "browser": "apps/virtual-table-demo/main.ts", + "tsConfig": "apps/virtual-table-demo/tsconfig.app.json", + "inlineStyleLanguage": "scss", "assets": ["apps/virtual-table-demo/assets"], - "polyfills": "apps/virtual-table-demo/polyfills.ts", "styles": [ ".global/integration-app.style.scss", "node_modules/ngx-toastr/toastr.css", "apps/virtual-table-demo/styles.scss" ], - "aot": false, - "vendorChunk": true, - "extractLicenses": false, - "buildOptimizer": false, - "sourceMap": true, - "optimization": false, - "namedChunks": true + "scripts": [], + "stylePreprocessorOptions": { + "includePaths": ["./src"], + "sass": { + "silenceDeprecations": ["import"] + } + } }, "configurations": { "production": { - "fileReplacements": [ + "budgets": [ { - "replace": "apps/virtual-table-demo/environments/environment.ts", - "with": "apps/virtual-table-demo/environments/environment.prod.ts" + "type": "initial", + "maximumWarning": "1mb", + "maximumError": "2mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" } ], - "optimization": true, "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "buildOptimizer": true - }, - "development": { "fileReplacements": [ { - "replace": "apps/flex-layout-demo/environments/environment.ts", - "with": "apps/flex-layout-demo/environments/environment.prod.ts" + "replace": "apps/virtual-table-demo/environments/environment.ts", + "with": "apps/virtual-table-demo/environments/environment.prod.ts" } - ], - "optimization": false + ] + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true } }, "defaultConfiguration": "production", - "outputs": ["{projectRoot}/dist/virtual-table-demo"] + "outputs": ["{options.outputPath}"] }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "buildTarget": "virtual-table-demo:build" - }, + "executor": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "virtual-table-demo:build:production" diff --git a/apps/virtual-table-demo/src/app-routing.module.ts b/apps/virtual-table-demo/src/app-routing.module.ts deleted file mode 100644 index b42837a08..000000000 --- a/apps/virtual-table-demo/src/app-routing.module.ts +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable */ -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -@NgModule({ - imports: [ - RouterModule.forRoot( - [ - { - path: '', - pathMatch: 'full', - redirectTo: 'samples', - }, - { - path: 'samples', - children: [ - { - path: '', - pathMatch: 'full', - redirectTo: 'guide', - }, - { - path: 'guide', - loadChildren: () => - import('./samples/guide/guide.module').then( - (m) => m.GuideModule, - ), - }, - { - path: 'first', - loadChildren: () => - import('./samples/sample-first/sample-first.module').then( - (m) => m.SampleFirstModule, - ), - }, - { - path: 'first-second', - loadChildren: () => - import( - './samples/sample-first-second/sample-first-second.module' - ).then((m) => m.SampleFirstSecondModule), - }, - { - path: 'second', - loadChildren: () => - import( - './samples/sample-second/sample-second.module' - ).then((m) => m.SampleSecondModule), - }, - { - path: 'third', - loadChildren: () => - import('./samples/sample-third/sample-third.module').then( - (m) => m.SampleThirdModule, - ), - }, - { - path: 'fourth', - loadChildren: () => - import( - './samples/sample-fourth/sample-fourth.module' - ).then((m) => m.SampleFourthModule), - }, - { - path: 'five', - loadChildren: () => - import('./samples/sample-five/sample-five.module').then( - (m) => m.SampleFiveModule, - ), - }, - { - path: 'six', - loadChildren: () => - import('./samples/sample-six/sample-six.module').then( - (m) => m.SampleSixModule, - ), - }, - { - path: 'seven', - loadChildren: () => - import('./samples/sample-seven/sample-seven.module').then( - (m) => m.SampleSevenModule, - ), - }, - { - path: 'eight', - loadChildren: () => - import('./samples/sample-eight/sample-eight.module').then( - (m) => m.SampleEightModule, - ), - }, - { - path: 'night', - loadChildren: () => - import('./samples/sample-night/sample-night.module').then( - (m) => m.SampleNightModule, - ), - }, - { - path: 'eleven', - loadChildren: () => - import( - './samples/sample-eleven/sample-eleven.module' - ).then((m) => m.SampleElevenModule), - }, - { - path: 'twelve', - loadChildren: () => - import( - './samples/sample-twelve/sample-twelve.module' - ).then((m) => m.SampleTwelveModule), - }, - { - path: 'thirteen', - loadChildren: () => - import( - './samples/sample-thirteen/sample-thirteen.module' - ).then((m) => m.SampleThirteenModule), - }, - { - path: 'fourteen', - loadChildren: () => - import( - './samples/sample-fourteen/sample-fourteen.module' - ).then((m) => m.SampleFourteenModule), - }, - { - path: 'fifteen', - loadChildren: () => - import( - './samples/sample-fifteen/sample-fifteen.module' - ).then((m) => m.SampleFifteenModule), - }, - { - path: 'sixteen', - loadChildren: () => - import( - './samples/sample-sixteen/sample-sixteen.module' - ).then((m) => m.SampleSixteenModule), - }, - { - path: 'seventeen', - loadChildren: () => - import( - './samples/sample-seventeen/sample-seventeen.module' - ).then((m) => m.SampleSeventeenModule), - }, - { - path: 'eighteen', - loadChildren: () => - import( - './samples/sample-eighteen/sample-eighteen.module' - ).then((m) => m.SampleEighteenModule), - }, - ], - }, - ], - {useHash: true, scrollPositionRestoration: 'enabled'}, - ), - ], - exports: [RouterModule], -}) -export class AppRoutingModule {} diff --git a/apps/virtual-table-demo/src/app.component.ts b/apps/virtual-table-demo/src/app.component.ts index cf11b2953..0eed43f28 100644 --- a/apps/virtual-table-demo/src/app.component.ts +++ b/apps/virtual-table-demo/src/app.component.ts @@ -1,7 +1,23 @@ import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MatDivider} from '@angular/material/divider'; +import {MatList, MatListItem} from '@angular/material/list'; +import {MatDrawer, MatDrawerContainer, MatDrawerContent} from '@angular/material/sidenav'; +import {MatToolbar} from '@angular/material/toolbar'; +import {RouterLink, RouterOutlet} from '@angular/router'; @Component({ selector: 'app-root', + imports: [ + MatDivider, + MatDrawer, + MatDrawerContainer, + MatDrawerContent, + MatList, + MatListItem, + MatToolbar, + RouterLink, + RouterOutlet, + ], templateUrl: './app.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/apps/virtual-table-demo/src/app.config.ts b/apps/virtual-table-demo/src/app.config.ts new file mode 100644 index 000000000..da131222a --- /dev/null +++ b/apps/virtual-table-demo/src/app.config.ts @@ -0,0 +1,53 @@ +import {provideHttpClient} from '@angular/common/http'; +import type {ApplicationConfig} from '@angular/core'; +import { + provideBrowserGlobalErrorListeners, + provideZonelessChangeDetection, +} from '@angular/core'; +import {provideNativeDateAdapter} from '@angular/material/core'; +import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field'; +import {provideAnimationsAsync} from '@angular/platform-browser/animations/async'; +import { + PreloadAllModules, + provideRouter, + withComponentInputBinding, + withHashLocation, + withInMemoryScrolling, + withPreloading, + withRouterConfig, +} from '@angular/router'; +import {provideVirtualTable} from '@angular-ru/cdk/virtual-table'; +import {provideToastr} from 'ngx-toastr'; + +import {routes} from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideZonelessChangeDetection(), + provideBrowserGlobalErrorListeners(), + provideRouter( + routes, + withPreloading(PreloadAllModules), + withRouterConfig({ + paramsInheritanceStrategy: 'always', + }), + withComponentInputBinding(), + withInMemoryScrolling({ + scrollPositionRestoration: 'enabled', + }), + withHashLocation(), + ), + provideHttpClient(), + provideAnimationsAsync(), + provideVirtualTable(), + provideToastr(), + { + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, + useValue: { + subscriptSizing: 'dynamic', + appearance: 'outline', + }, + }, + provideNativeDateAdapter(), + ], +}; diff --git a/apps/virtual-table-demo/src/app.module.ts b/apps/virtual-table-demo/src/app.module.ts deleted file mode 100644 index 95282ad42..000000000 --- a/apps/virtual-table-demo/src/app.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {HttpClientModule} from '@angular/common/http'; -import {NgModule} from '@angular/core'; -import {BrowserModule} from '@angular/platform-browser'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {ToastrModule} from 'ngx-toastr'; - -import {AppComponent} from './app.component'; -import {AppRoutingModule} from './app-routing.module'; -import {SharedModule} from './shared/shared.module'; - -@NgModule({ - imports: [ - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - HttpClientModule, - SharedModule, - ToastrModule.forRoot(), - ], - declarations: [AppComponent], - bootstrap: [AppComponent], -}) -export class AppModule {} diff --git a/apps/virtual-table-demo/src/app.routes.ts b/apps/virtual-table-demo/src/app.routes.ts new file mode 100644 index 000000000..66e1b51ba --- /dev/null +++ b/apps/virtual-table-demo/src/app.routes.ts @@ -0,0 +1,113 @@ +import type {Routes} from '@angular/router'; + +export const routes: Routes = [ + { + path: '', + pathMatch: 'full', + redirectTo: 'samples', + }, + { + path: 'samples', + children: [ + { + path: '', + pathMatch: 'full', + redirectTo: 'guide', + }, + { + path: 'guide', + loadComponent: async () => import('./samples/guide/guide.component'), + }, + { + path: 'first', + loadComponent: async () => + import('./samples/sample-first/sample-first.component'), + }, + { + path: 'first-second', + loadComponent: async () => + import('./samples/sample-first-second/sample-first-second.component'), + }, + { + path: 'second', + loadComponent: async () => + import('./samples/sample-second/sample-second.component'), + }, + { + path: 'third', + loadComponent: async () => + import('./samples/sample-third/sample-third.component'), + }, + { + path: 'fourth', + loadComponent: async () => + import('./samples/sample-fourth/sample-fourth.component'), + }, + { + path: 'five', + loadComponent: async () => + import('./samples/sample-five/sample-five.component'), + }, + { + path: 'six', + loadComponent: async () => + import('./samples/sample-six/sample-six.component'), + }, + { + path: 'seven', + loadComponent: async () => + import('./samples/sample-seven/sample-seven.component'), + }, + { + path: 'eight', + loadComponent: async () => + import('./samples/sample-eight/sample-eight.component'), + }, + { + path: 'night', + loadComponent: async () => + import('./samples/sample-night/sample-night.component'), + }, + { + path: 'eleven', + loadComponent: async () => + import('./samples/sample-eleven/sample-eleven.component'), + }, + { + path: 'twelve', + loadComponent: async () => + import('./samples/sample-twelve/sample-twelve.component'), + }, + { + path: 'thirteen', + loadComponent: async () => + import('./samples/sample-thirteen/sample-thirteen.component'), + }, + { + path: 'fourteen', + loadComponent: async () => + import('./samples/sample-fourteen/sample-fourteen.component'), + }, + { + path: 'fifteen', + loadComponent: async () => + import('./samples/sample-fifteen/sample-fifteen.component'), + }, + { + path: 'sixteen', + loadComponent: async () => + import('./samples/sample-sixteen/sample-sixteen.component'), + }, + { + path: 'seventeen', + loadComponent: async () => + import('./samples/sample-seventeen/sample-seventeen.component'), + }, + { + path: 'eighteen', + loadComponent: async () => + import('./samples/sample-eighteen/sample-eighteen.component'), + }, + ], + }, +]; diff --git a/apps/virtual-table-demo/src/mocks-generator.ts b/apps/virtual-table-demo/src/mocks-generator.ts index 2573abdb4..e2f84b1df 100644 --- a/apps/virtual-table-demo/src/mocks-generator.ts +++ b/apps/virtual-table-demo/src/mocks-generator.ts @@ -1,84 +1,25 @@ -/* eslint-disable no-magic-numbers,@typescript-eslint/no-magic-numbers,max-classes-per-file */ import {PlainObject} from '@angular-ru/cdk/typings'; -import {WebWorkerThreadService} from '@angular-ru/cdk/webworker'; export class MocksGenerator { - // eslint-disable-next-line max-lines-per-function public static async generator( rowsNumber: number, colsNumber: number, startIndex = 0, ): Promise { - return new WebWorkerThreadService().run( - // eslint-disable-next-line max-lines-per-function - (data: any): PlainObject[] => { - class FakeGenerator { - // eslint-disable-next-line max-lines-per-function - public static generateTable( - rows: number, - cols: number, - start: number, - ): PlainObject[] { - const startDate: Date = new Date(); - const endDate: Date = new Date( - new Date().setFullYear(new Date().getFullYear() + 1), - ); - - // eslint-disable-next-line max-lines-per-function - return new Array(rows).fill(0).map( - // eslint-disable-next-line max-lines-per-function - (_: unknown, index: number): PlainObject => { - const idx: number = start + index + 1; - - const baseRow: PlainObject = { - id: idx, - reverseId: - Math.round( - Math.random() + - rows * 512 + - cols + - start * 10, - ) * 1024, - someDate: new Date( - startDate.getTime() + - Math.random() * - (endDate.getTime() - startDate.getTime()), - ).getTime(), - name: `Random - ${((Math.random() + 1) * 100).toFixed(0)}__${idx}`, - description: `Random - ${((Math.random() + 1) * 100).toFixed(0)}__${idx}`, - guid: `${'5cdae5b2ba0a57f709b72142' + '__'}${idx}`, - someBoolean: Math.random() > 0.5, - someNull: Math.random() > 0.5 ? null : 'not null', - }; - - // eslint-disable-next-line @typescript-eslint/typedef,@typescript-eslint/explicit-function-return-type - const random = (min: number, max: number) => - min + Math.random() * (max - min); - - if (cols > 7) { - baseRow[ - 'About Big Text And More Powerful Label Fugiat Tempor Sunt Nostrud' - ] = new Array(Math.ceil(random(0, 1000))) - .fill(null) - .map((): string => - (~~(Math.random() * 36)).toString(36), - ) - .join(''); - - for (let i = 7; i <= cols - 1; i++) { - baseRow[`column-${i}`] = `$row-${idx} $col-${i}`; - } - } - - return baseRow; - }, - ); - } - } - - return FakeGenerator.generateTable(data.rows, data.cols, data.start); + const worker = new Worker(new URL('./mocks-generator.worker', import.meta.url)); + + return new Promise( + (resolve: (value: PlainObject[]) => void): void => { + worker.onmessage = ({data}: {data: PlainObject[]}) => { + resolve(data); + }; + + worker.postMessage({ + rows: rowsNumber, + cols: colsNumber, + start: startIndex, + }); }, - {rows: rowsNumber, cols: colsNumber, start: startIndex}, ); } } diff --git a/apps/virtual-table-demo/src/mocks-generator.worker.ts b/apps/virtual-table-demo/src/mocks-generator.worker.ts new file mode 100644 index 000000000..9f86ddb39 --- /dev/null +++ b/apps/virtual-table-demo/src/mocks-generator.worker.ts @@ -0,0 +1,67 @@ +import {PlainObject} from '@angular-ru/cdk/typings'; +import { + randAwsRequestId, + randBetweenDate, + randBoolean, + randCatchPhrase, + randNumber, + randProductDescription, + randProductName, + randUuid, +} from '@ngneat/falso'; + +interface DataType { + rows: number; + cols: number; + start: number; +} + +addEventListener('message', ({data}: {data: DataType}) => { + const {rows, cols, start} = data; + const startDate: Date = new Date(); + const endDate: Date = new Date(new Date().setFullYear(new Date().getFullYear() + 1)); + + const generatedData = Array.from( + { + length: rows, + }, + (_: unknown, index: number): PlainObject => { + const idx: number = start + index + 1; + const reverseIdMin = (rows * 512 + cols + start * 10) * 1024; + const reverseIdMax = reverseIdMin + 1024; + + const baseRow: PlainObject = { + id: idx, + reverseId: randNumber({ + min: reverseIdMin, + max: reverseIdMax, + }), + someDate: randBetweenDate({ + from: startDate, + to: endDate, + }).toLocaleDateString(), + name: randProductName({ + maxCharCount: 20, + }), + description: randProductDescription(), + uuid: randUuid(), + someBoolean: randBoolean(), + someNull: randBoolean() ? null : 'not null', + }; + + if (cols > 7) { + baseRow[randCatchPhrase()] = randAwsRequestId({ + maxCharCount: 1e3, + }); + + for (let i = 7; i <= cols - 1; i++) { + baseRow[`column-${i}`] = `$row-${idx} $col-${i}`; + } + } + + return baseRow; + }, + ); + + postMessage(generatedData); +}); diff --git a/apps/virtual-table-demo/src/samples/guide/guide.component.html b/apps/virtual-table-demo/src/samples/guide/guide.component.html index e2646b17e..7fd0cac87 100644 --- a/apps/virtual-table-demo/src/samples/guide/guide.component.html +++ b/apps/virtual-table-demo/src/samples/guide/guide.component.html @@ -35,15 +35,14 @@

Guide overview


-    import { TableBuilderModule } from '@angular-ru/cdk/virtual-table';
+    import { provideVirtualTable } from '@angular-ru/cdk/virtual-table';
 
-    @NgModule({
-       imports: [
-          TableBuilderModule.forRoot()
+    export const appConfig: ApplicationConfig = {
+       providers: [
+          provideVirtualTable()
        ],
        ...
-    })
-    export class AppModule { }
+    };
     

Next, let's declare the basic grid configuration. Edit src/app.component.ts:

@@ -51,10 +50,12 @@

Guide overview


     import { Component } from '@angular/core';
     import { MyData } from './my-data.interface';
+    import { VirtualTable } from '@angular-ru/cdk/virtual-table';
 
     @Component({
       selector: 'app-root',
-      template: `<ngx-table-builder [source]="data"></ngx-table-builder>`
+      template: `<ngx-table-builder [source]="data"></ngx-table-builder>`,
+      imports: [VirtualTable]
     })
     export class AppComponent {
       public data: MyData[] = [
diff --git a/apps/virtual-table-demo/src/samples/guide/guide.component.ts b/apps/virtual-table-demo/src/samples/guide/guide.component.ts
index 6b90e1734..8be49dae9 100644
--- a/apps/virtual-table-demo/src/samples/guide/guide.component.ts
+++ b/apps/virtual-table-demo/src/samples/guide/guide.component.ts
@@ -1,14 +1,17 @@
 import {AfterViewInit, ChangeDetectionStrategy, Component} from '@angular/core';
+import {RouterLink} from '@angular/router';
 import {PlainObject} from '@angular-ru/cdk/typings';
+import {VirtualTable} from '@angular-ru/cdk/virtual-table';
 
 import {hlJsCode} from '../../../../../.global/utils/hljs-code';
 
 @Component({
     selector: 'guide',
+    imports: [RouterLink, VirtualTable],
     templateUrl: './guide.component.html',
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class GuideComponent implements AfterViewInit {
+export default class GuideComponent implements AfterViewInit {
     public rowData: PlainObject[] = [
         {make: 'Toyota', model: 'Celica', price: 35000},
         {make: 'Ford', model: 'Mondeo', price: 32000},
diff --git a/apps/virtual-table-demo/src/samples/guide/guide.module.ts b/apps/virtual-table-demo/src/samples/guide/guide.module.ts
deleted file mode 100644
index e19ba5a67..000000000
--- a/apps/virtual-table-demo/src/samples/guide/guide.module.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {CommonModule} from '@angular/common';
-import {NgModule} from '@angular/core';
-import {RouterModule} from '@angular/router';
-
-import {SharedModule} from '../../shared/shared.module';
-import {GuideComponent} from './guide.component';
-
-@NgModule({
-    imports: [
-        CommonModule,
-        SharedModule,
-        RouterModule.forChild([{path: '', component: GuideComponent}]),
-    ],
-    declarations: [GuideComponent],
-})
-export class GuideModule {}
diff --git a/apps/virtual-table-demo/src/samples/sample-eight/sample-eight.component.html b/apps/virtual-table-demo/src/samples/sample-eight/sample-eight.component.html
index 40b52f54a..8bdb32d82 100644
--- a/apps/virtual-table-demo/src/samples/sample-eight/sample-eight.component.html
+++ b/apps/virtual-table-demo/src/samples/sample-eight/sample-eight.component.html
@@ -19,7 +19,7 @@
 
     
         
         
     
 
-    
-        
-            
-                {{ symbol }}
-            
-        
-
-        
-            {{ cell }}
-        
-    
+    @for (key of table.modelColumnKeys; track key) {
+        
+            @if (key === 'symbol') {
+                
+                    {{ symbol }}
+                
+            } @else {
+                
+                    {{ cell }}
+                
+            }
+        
+    }
 
      = null;
     private timeout: Nullable = null;
-    public data: PlainObject[] = [];
+    public data = signal([]);
     public regenerate = false;
 
-    constructor(
-        private readonly cd: ChangeDetectorRef,
-        private readonly ngZone: NgZone,
-    ) {}
-
     public ngOnInit(): void {
         this.updateTable();
         const DEFAULT_TIMEOUT = 14500;
@@ -95,7 +81,6 @@ export class SampleEightComponent implements OnInit, AfterViewInit, OnDestroy {
             this.idInterval = window.setInterval((): void => {
                 if (this.regenerate) {
                     this.updateTable();
-                    this.cd.detectChanges();
                 }
             }, DEFAULT_TIMEOUT);
         });
@@ -108,8 +93,7 @@ export class SampleEightComponent implements OnInit, AfterViewInit, OnDestroy {
     public updateRow(row: PlainObject, key: string, value: T): void {
         const newRow: PlainObject = {...row, [key]: value};
 
-        this.data = replaceAt(this.data, this.data.indexOf(row), newRow);
-        detectChanges(this.cd);
+        this.data.set(replaceAt(this.data(), this.data().indexOf(row), newRow));
     }
 
     public asyncRow(row: PlainObject, key: string, value: T): void {
@@ -129,46 +113,38 @@ export class SampleEightComponent implements OnInit, AfterViewInit, OnDestroy {
 
     // eslint-disable-next-line max-lines-per-function
     private updateTable(): void {
-        const length = 1000;
-
-        this.data = new Array(length).fill(0).map(
-            // eslint-disable-next-line max-lines-per-function
-            (_: PlainObject, index: number): any => ({
-                id: index,
-                symbol: COLORS[Math.round(Math.random() * (COLORS.length - 1))],
-                item:
-                    'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of' +
-                    ' classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin' +
-                    ' professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, ' +
-                    'consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical ' +
-                    'literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 ' +
-                    'of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC.' +
-                    ' This book is a treatise on the theory of ethics, very popular during the Renaissance. The first' +
-                    ' line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.\n' +
-                    '\n' +
-                    'The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. ' +
-                    'Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in' +
-                    ' their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.',
-                // eslint-disable-next-line @typescript-eslint/no-magic-numbers
-                cost: Math.floor(Math.random() * 100) + 1,
-                active: true,
-                name: `${NAMES[Math.round(Math.random() * (NAMES.length - 1))]} ${NAMES[
-                    Math.round(Math.random() * (NAMES.length - 1))
-                ]?.charAt(0)}.`,
-                // eslint-disable-next-line @typescript-eslint/no-magic-numbers
-                weight: Math.round(Math.random() * 100).toString(),
-                firstName: NAMES[Math.round(Math.random() * (NAMES.length - 1))],
-                lastName: NAMES[Math.round(Math.random() * (NAMES.length - 1))],
-                dateOfBirth: 1985,
-                spokenLanguages: {
-                    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
-                    native: `English${Math.round(Math.random() * 100).toString()}`,
-                    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
-                    fluent: `Spanish${Math.round(Math.random() * 100).toString()}`,
-                    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
-                    intermediate: `Chinese${Math.round(Math.random() * 100).toString()}`,
+        this.data.set(
+            Array.from(
+                {
+                    length: 1e3,
                 },
-            }),
+                (_: PlainObject, index: number): any => ({
+                    id: index,
+                    symbol: randColor(),
+                    item: randLines(),
+                    cost: randNumber({
+                        min: 1,
+                        max: 100,
+                    }),
+                    active: randBoolean(),
+                    name: randFullName(),
+                    weight: randNumber({
+                        min: 50,
+                        max: 100,
+                    }),
+                    firstName: randFirstName(),
+                    lastName: randLastName(),
+                    dateOfBirth: randBetweenDate({
+                        from: new Date(1980, 0, 1),
+                        to: new Date(2000, 0, 1),
+                    }).toLocaleDateString(),
+                    spokenLanguages: {
+                        native: `${randLanguage()} (${randNumber({max: 100})})`,
+                        fluent: `${randLanguage()} (${randNumber({max: 100})})`,
+                        intermediate: `${randLanguage()} (${randNumber({max: 100})})`,
+                    },
+                }),
+            ),
         );
     }
 }
diff --git a/apps/virtual-table-demo/src/samples/sample-eight/sample-eight.module.ts b/apps/virtual-table-demo/src/samples/sample-eight/sample-eight.module.ts
deleted file mode 100644
index 6bae4a1e3..000000000
--- a/apps/virtual-table-demo/src/samples/sample-eight/sample-eight.module.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {CommonModule} from '@angular/common';
-import {NgModule} from '@angular/core';
-import {RouterModule} from '@angular/router';
-
-import {SharedModule} from '../../shared/shared.module';
-import {SampleEightComponent} from './sample-eight.component';
-
-@NgModule({
-    imports: [
-        CommonModule,
-        SharedModule,
-        RouterModule.forChild([{path: '', component: SampleEightComponent}]),
-    ],
-    declarations: [SampleEightComponent],
-})
-export class SampleEightModule {}
diff --git a/apps/virtual-table-demo/src/samples/sample-eighteen/sample-eighteen.component.html b/apps/virtual-table-demo/src/samples/sample-eighteen/sample-eighteen.component.html
index c5173da88..b498ea301 100644
--- a/apps/virtual-table-demo/src/samples/sample-eighteen/sample-eighteen.component.html
+++ b/apps/virtual-table-demo/src/samples/sample-eighteen/sample-eighteen.component.html
@@ -3,6 +3,7 @@
 
     
@@ -10,7 +12,7 @@
 
 
     
diff --git a/apps/virtual-table-demo/src/samples/sample-fifteen/sample-fifteen.component.ts b/apps/virtual-table-demo/src/samples/sample-fifteen/sample-fifteen.component.ts
index c65af210e..b2a027cd2 100644
--- a/apps/virtual-table-demo/src/samples/sample-fifteen/sample-fifteen.component.ts
+++ b/apps/virtual-table-demo/src/samples/sample-fifteen/sample-fifteen.component.ts
@@ -1,36 +1,39 @@
 import {
     AfterViewInit,
     ChangeDetectionStrategy,
-    ChangeDetectorRef,
     Component,
+    inject,
     OnInit,
+    signal,
 } from '@angular/core';
+import {MatButton} from '@angular/material/button';
 import {MatDialog} from '@angular/material/dialog';
+import {MatToolbar} from '@angular/material/toolbar';
 import {PlainObject} from '@angular-ru/cdk/typings';
-import {TableUpdateSchema} from '@angular-ru/cdk/virtual-table';
+import type {TableUpdateSchema} from '@angular-ru/cdk/virtual-table';
+import {VirtualTable} from '@angular-ru/cdk/virtual-table';
 
 import {hlJsCode} from '../../../../../.global/utils/hljs-code';
 import {MocksGenerator} from '../../mocks-generator';
+import {CodeDialogComponent} from '../../shared/dialog/code-dialog.component';
 
 @Component({
     selector: 'sample-fifteen',
+    imports: [MatButton, MatToolbar, VirtualTable],
     templateUrl: './sample-fifteen.component.html',
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class SampleFifteenComponent implements OnInit, AfterViewInit {
-    public data: PlainObject[] = [];
-    constructor(
-        public readonly dialog: MatDialog,
-        private readonly cd: ChangeDetectorRef,
-    ) {}
+export default class SampleFifteenComponent implements OnInit, AfterViewInit {
+    private readonly dialog = inject(MatDialog);
+
+    public data = signal([]);
 
     public ngOnInit(): void {
         const rows = 10000;
         const cols = 59;
 
         MocksGenerator.generator(rows, cols).then((data: PlainObject[]): void => {
-            this.data = data;
-            this.cd.detectChanges();
+            this.data.set(data);
         });
     }
 
@@ -42,4 +45,21 @@ export class SampleFifteenComponent implements OnInit, AfterViewInit {
         // eslint-disable-next-line no-console
         console.log(event);
     }
+
+    protected showSample(): void {
+        this.dialog.open(CodeDialogComponent, {
+            data: {
+                title: 'Overview drag-and-drop table',
+                description: '',
+                code: `
+
+    
+
+                `,
+            },
+        });
+    }
 }
diff --git a/apps/virtual-table-demo/src/samples/sample-fifteen/sample-fifteen.module.ts b/apps/virtual-table-demo/src/samples/sample-fifteen/sample-fifteen.module.ts
deleted file mode 100644
index 9c362e75a..000000000
--- a/apps/virtual-table-demo/src/samples/sample-fifteen/sample-fifteen.module.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {CommonModule} from '@angular/common';
-import {NgModule} from '@angular/core';
-import {RouterModule} from '@angular/router';
-
-import {SharedModule} from '../../shared/shared.module';
-import {SampleFifteenComponent} from './sample-fifteen.component';
-
-@NgModule({
-    imports: [
-        CommonModule,
-        SharedModule,
-        RouterModule.forChild([{path: '', component: SampleFifteenComponent}]),
-    ],
-    declarations: [SampleFifteenComponent],
-})
-export class SampleFifteenModule {}
diff --git a/apps/virtual-table-demo/src/samples/sample-first-second/sample-first-second.component.html b/apps/virtual-table-demo/src/samples/sample-first-second/sample-first-second.component.html
index 3721b52f3..97a8ecdd6 100644
--- a/apps/virtual-table-demo/src/samples/sample-first-second/sample-first-second.component.html
+++ b/apps/virtual-table-demo/src/samples/sample-first-second/sample-first-second.component.html
@@ -4,6 +4,7 @@
 
         
             
         
         
@@ -80,15 +78,15 @@
- + Filter search - + Find options - - {{ type.key }} - + @for (type of table2.filterable.types | keyvalue; track type.key) { + + {{ type.key }} + + }
- - {{ key }} - - {{ cell }} - - + @for (key of table2.modelColumnKeys; track key) { + + {{ key }} + + {{ cell }} + + + }
diff --git a/apps/virtual-table-demo/src/samples/sample-night/sample-night.component.ts b/apps/virtual-table-demo/src/samples/sample-night/sample-night.component.ts index f8ad9fd5c..f2d9b3009 100644 --- a/apps/virtual-table-demo/src/samples/sample-night/sample-night.component.ts +++ b/apps/virtual-table-demo/src/samples/sample-night/sample-night.component.ts @@ -1,14 +1,22 @@ +import {KeyValuePipe} from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, - Injector, - NgZone, + inject, OnInit, + signal, } from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {MatButton} from '@angular/material/button'; +import {MatCheckbox} from '@angular/material/checkbox'; import {MatDialog} from '@angular/material/dialog'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField, MatInput, MatLabel, MatSuffix} from '@angular/material/input'; +import {MatOption, MatSelect} from '@angular/material/select'; +import {MatToolbar} from '@angular/material/toolbar'; import {PlainObject} from '@angular-ru/cdk/typings'; +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; import {hlJsCode} from '../../../../../.global/utils/hljs-code'; import {MocksGenerator} from '../../mocks-generator'; @@ -16,23 +24,29 @@ import {CodeDialogComponent} from '../../shared/dialog/code-dialog.component'; @Component({ selector: 'sample-night', + imports: [ + FormsModule, + KeyValuePipe, + MatButton, + MatCheckbox, + MatFormField, + MatIcon, + MatInput, + MatLabel, + MatOption, + MatSelect, + MatSuffix, + MatToolbar, + VirtualTable, + ], templateUrl: './sample-night.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SampleNightComponent implements OnInit, AfterViewInit { - private readonly ngZone: NgZone; - public dataFirst: PlainObject[] = []; - public dataSecond: PlainObject[] = []; - public nativeScrollbar = false; - public readonly dialog: MatDialog; - - constructor( - private readonly cd: ChangeDetectorRef, - injector: Injector, - ) { - this.dialog = injector.get(MatDialog); - this.ngZone = injector.get(NgZone); - } +export default class SampleNightComponent implements OnInit, AfterViewInit { + public dataFirst = signal([]); + public dataSecond = signal([]); + public nativeScrollbar = signal(false); + public readonly dialog = inject(MatDialog); public ngOnInit(): void { const rows1 = 11; @@ -45,9 +59,8 @@ export class SampleNightComponent implements OnInit, AfterViewInit { MocksGenerator.generator(rows1, cols1), MocksGenerator.generator(rows2, cols2), ]).then(([first, second]: [PlainObject[], PlainObject[]]): void => { - this.dataFirst = first; - this.dataSecond = second; - this.cd.detectChanges(); + this.dataFirst.set(first); + this.dataSecond.set(second); }); } @@ -55,15 +68,6 @@ export class SampleNightComponent implements OnInit, AfterViewInit { hlJsCode(); } - public update(): void { - this.ngZone.runOutsideAngular((): void => { - // eslint-disable-next-line no-restricted-globals - setTimeout((): void => { - this.cd.detectChanges(); - }); - }); - } - // eslint-disable-next-line max-lines-per-function public showSample(): void { this.dialog.open(CodeDialogComponent, { @@ -90,17 +94,15 @@ export class SampleNightComponent implements OnInit, AfterViewInit {
- +
- +
`, }, - height: '750px', - width: '700px', }); } } diff --git a/apps/virtual-table-demo/src/samples/sample-night/sample-night.module.ts b/apps/virtual-table-demo/src/samples/sample-night/sample-night.module.ts deleted file mode 100644 index d0a50fd7e..000000000 --- a/apps/virtual-table-demo/src/samples/sample-night/sample-night.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -import {SharedModule} from '../../shared/shared.module'; -import {SampleNightComponent} from './sample-night.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild([{path: '', component: SampleNightComponent}]), - ], - declarations: [SampleNightComponent], -}) -export class SampleNightModule {} diff --git a/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.html b/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.html index cc90b45f8..26bd2ff8f 100644 --- a/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.html +++ b/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.html @@ -7,68 +7,68 @@

1. Override columns rendering


-  // app.component.ts
-  import { Component } from "@angular/core";
-  import { LicenseSample } from "./license.interface";
+      // app.component.ts
+      import { Component } from "@angular/core";
+      import { LicenseSample } from "./license.interface";
 
-  @Component({
-    selector: 'app',
-    templateUrl: './app.component.html'
-  })
-  export class AppComponent {
+      @Component({
+      selector: 'app',
+      templateUrl: './app.component.html'
+      })
+      export class AppComponent {
 
-    public licenses: LicenseSample[] = [
+      public licenses: LicenseSample[] = [
       {
-        id: 1,
-        name: 'single',
-        price: 29.3
+      id: 1,
+      name: 'single',
+      price: 29.3
       },
       {
-        id: 2,
-        name: 'developer',
-        price: 49.8
+      id: 2,
+      name: 'developer',
+      price: 49.8
       },
       {
-        id: 3,
-        name: 'premium',
-        price: 99.5
+      id: 3,
+      name: 'premium',
+      price: 99.5
       },
       {
-        id: 4,
-        name: 'enterprise',
-        price: 199
+      id: 4,
+      name: 'enterprise',
+      price: 199
       }
-    ];
+      ];
 
-  }
+      }
 
-
+

-    <!-- app.component.html -->
-    <ngx-table-builder [source]="licenses">
+      <!-- app.component.html -->
+      <ngx-table-builder [source]="licenses">
 
       <ngx-column key="name">
-          <ng-template ngx-th>License</ng-template>
-          <ng-template ngx-td let-name>
-            {{ name | uppercase }}
-          </ng-template>
+      <ng-template ngx-th>License</ng-template>
+      <ng-template ngx-td let-name>
+      {{ name | uppercase }}
+      </ng-template>
       </ngx-column>
 
       <ngx-column key="price">
-          <ng-template ngx-th>Cost</ng-template>
-          <ng-template ngx-td let-price>
-            {{ price | currency }}
-          </ng-template>
+      <ng-template ngx-th>Cost</ng-template>
+      <ng-template ngx-td let-price>
+      {{ price | currency }}
+      </ng-template>
       </ngx-column>
 
-    </ngx-table-builder>
+      </ngx-table-builder>
 
-
+ - + License 2. Sticky and Custom Columns

-  // app.component.ts
-  import { Component } from "@angular/core";
+      // app.component.ts
+      import { Component } from "@angular/core";
 
-  export interface PeriodicElement {
+      export interface PeriodicElement {
       name: string;
       position: number;
       weight: number;
       symbol: string;
-  }
+      }
 
-  @Component({
-    selector: 'app',
-    templateUrl: './app.component.html',
-    styles: [
+      @Component({
+      selector: 'app',
+      templateUrl: './app.component.html',
+      styles: [
       `
-         // Use custom CSS for column and cell
-        .status-column .table-grid__cell {
-          padding: 0;
-          color: green;
-        }
-
-         // Reset CSS for default mat-button style
-        .button__done[mat-button] {
-          padding: 0;
-          min-width: 100%;
-        }
+      // Use custom CSS for column and cell
+      .status-column .table-grid__cell {
+      padding: 0;
+      --mat-icon-color: green;
+      }
+
+      // Reset CSS for default mat-button style
+      .button__done[mat-button] {
+      padding: 0;
+      min-width: 100%;
+      }
       `
-    ],
-    // Use to disable CSS Encapsulation for this component
-    encapsulation: ViewEncapsulation.None,
-  })
-  export class AppComponent {
-    public columns: string[] = [
+      ],
+      // Use to disable CSS Encapsulation for this component
+      encapsulation: ViewEncapsulation.None,
+      })
+      export class AppComponent {
+      public columns: string[] = [
       'name', 'position', 'weight', 'symbol',
       'position', 'weight', 'symbol', 'status'
-    ];
+      ];
 
-    public data: PeriodicElement[] = [
+      public data: PeriodicElement[] = [
       { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
       { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
       { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
@@ -145,11 +145,11 @@ 

2. Sticky and Custom Columns

{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' }, { position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' }, { position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' }, - ]; - } + ]; + } -
+

Note @@ -173,45 +173,45 @@

2. Sticky and Custom Columns


-  <!-- app.component.html -->
-  <ngx-table-builder [source]="licenses" row-height="60">
+      <!-- app.component.html -->
+      <ngx-table-builder [source]="licenses" row-height="60">
       <ngx-column key="id" width="100" empty-head>
-          <ng-template ngx-td let-id>№{{ id }}</ng-template>
+      <ng-template ngx-td let-id>№{{ id }}</ng-template>
       </ngx-column>
 
       <ngx-column key="name" head-title="License name">
-          <ng-template ngx-td let-name>{{ name | uppercase }}</ng-template>
+      <ng-template ngx-td let-name>{{ name | uppercase }}</ng-template>
       </ngx-column>
 
       <ngx-column key="price" head-title="Cost">
-          <ng-template ngx-td let-price>
-              <button mat-button [matMenuTriggerFor]="menu">{{ price | currency }}</button>
-              <mat-menu #menu="matMenu">
-                  <button mat-menu-item>EUR</button>
-                  <button mat-menu-item>DOL</button>
-              </mat-menu>
-          </ng-template>
+      <ng-template ngx-td let-price>
+      <button mat-button [matMenuTriggerFor]="menu">{{ price | currency }}</button>
+      <mat-menu #menu="matMenu">
+      <button mat-menu-item>EUR</button>
+      <button mat-menu-item>DOL</button>
+      </mat-menu>
+      </ng-template>
       </ngx-column>
 
       <ngx-column key="change" width="250" empty-head custom-key>
-          <ng-template ngx-td>
-              <mat-form-field>
-                  <mat-label>Relative</mat-label>
-                  <mat-select>
-                      <mat-option
-                          [value]="license.name"
-                          (click)="cd.detectChanges()"
-                          *ngFor="let license of licenses"
-                      >
-                          {{ license.name }}
-                      </mat-option>
-                  </mat-select>
-              </mat-form-field>
-          </ng-template>
+      <ng-template ngx-td>
+      <mat-form-field>
+      <mat-label>Relative</mat-label>
+      <mat-select>
+      <mat-option
+      [value]="license.name"
+      (click)="cd.detectChanges()"
+      *ngFor="let license of licenses"
+      >
+      {{ license.name }}
+      </mat-option>
+      </mat-select>
+      </mat-form-field>
+      </ng-template>
       </ngx-column>
-  </ngx-table-builder>
+      </ngx-table-builder>
 
-
+
@@ -235,6 +235,7 @@

2. Sticky and Custom Columns

- - + + @@ -325,13 +337,11 @@

2. Sticky and Custom Columns

Relative - - {{ license.name }} - + @for (license of licenses(); track license.id) { + + {{ license.name }} + + } diff --git a/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.ts b/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.ts index 84348bded..a56ede9fb 100644 --- a/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.ts +++ b/apps/virtual-table-demo/src/samples/sample-second/sample-second.component.ts @@ -1,10 +1,18 @@ +import {CurrencyPipe, UpperCasePipe} from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, + signal, ViewEncapsulation, } from '@angular/core'; +import {MatButton} from '@angular/material/button'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField, MatLabel} from '@angular/material/input'; +import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; +import {MatOption, MatSelect} from '@angular/material/select'; +import {MatToolbar} from '@angular/material/toolbar'; +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; import {hlJsCode} from '../../../../../.global/utils/hljs-code'; @@ -23,14 +31,28 @@ export interface PeriodicElement { @Component({ selector: 'sample-second', + imports: [ + CurrencyPipe, + MatButton, + MatFormField, + MatIcon, + MatLabel, + MatMenu, + MatMenuItem, + MatMenuTrigger, + MatOption, + MatSelect, + MatToolbar, + UpperCasePipe, + VirtualTable, + ], templateUrl: './sample-second.component.html', - // eslint-disable-next-line @angular-eslint/component-max-inline-declarations styles: [ ` /*noinspection CssUnusedSymbol*/ .status-column .table-grid__cell { padding: 0; - color: green; + --mat-icon-color: green; } /*noinspection CssUnusedSymbol*/ @@ -40,23 +62,13 @@ export interface PeriodicElement { } `, ], - // Use to disable CSS Encapsulation for this component encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SampleSecondComponent implements AfterViewInit { - public licenses: LicenseSample[] = []; +export default class SampleSecondComponent implements AfterViewInit { + public licenses = signal([]); - public columns: string[] = [ - 'name', - 'position', - 'weight', - 'symbol', - 'position', - 'weight', - 'symbol', - 'status', - ]; + public columns: string[] = ['name', 'position', 'weight', 'symbol', 'status']; // noinspection DuplicatedCode public elements: PeriodicElement[] = [ @@ -72,8 +84,6 @@ export class SampleSecondComponent implements AfterViewInit { {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'}, ]; - constructor(public readonly cd: ChangeDetectorRef) {} - // eslint-disable-next-line max-lines-per-function public ngAfterViewInit(): void { hlJsCode(); @@ -82,7 +92,7 @@ export class SampleSecondComponent implements AfterViewInit { setTimeout( // eslint-disable-next-line max-lines-per-function (): void => { - this.licenses = [ + this.licenses.set([ { id: 1, name: 'single', @@ -103,9 +113,7 @@ export class SampleSecondComponent implements AfterViewInit { name: 'enterprise', price: 199, }, - ]; - - this.cd.detectChanges(); + ]); }, ); } diff --git a/apps/virtual-table-demo/src/samples/sample-second/sample-second.module.ts b/apps/virtual-table-demo/src/samples/sample-second/sample-second.module.ts deleted file mode 100644 index dab17a6d8..000000000 --- a/apps/virtual-table-demo/src/samples/sample-second/sample-second.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -import {SharedModule} from '../../shared/shared.module'; -import {SampleSecondComponent} from './sample-second.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild([{path: '', component: SampleSecondComponent}]), - ], - declarations: [SampleSecondComponent], -}) -export class SampleSecondModule {} diff --git a/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.html b/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.html index 9d6e2aa0d..d2feb9e25 100644 --- a/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.html +++ b/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.html @@ -4,215 +4,225 @@ - - + - - - - - + + + - - {{ key }} - - {{ cell }} - - + @for (key of table.modelColumnKeys; track key) { + + {{ key }} + + {{ cell }} + + + } - - - - - - - + + + + + - - + - {{ key }} - - {{ cell }} - - + @for (key of table.modelColumnKeys; track key) { + + {{ key }} + + {{ cell }} + + + } - - - - - - - + + + + + - - - {{ 'ID' }} - - {{ id }} - - + + + + {{ 'ID' }} + + {{ id }} + + - - {{ 'NAME' }} - - {{ name }} - - + + {{ 'NAME' }} + + {{ name }} + + - - {{ 'DESCRIPTION' }} - - {{ description }} - - + + {{ 'DESCRIPTION' }} + + {{ description }} + + - + + +
+ +
+
+
+
+
+
+ + + - - -
+ + -
-
- -
-
- - - - - - - - + + - - {{ key }} - - {{ cell }} - - - + @for (key of table.modelColumnKeys; track key) { + + {{ key }} + + {{ cell }} + + + } + +
diff --git a/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.ts b/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.ts index 71dcf8633..eaafd3dda 100644 --- a/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.ts +++ b/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.component.ts @@ -1,32 +1,34 @@ import { AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, OnInit, + signal, } from '@angular/core'; +import {MatButton} from '@angular/material/button'; +import {MatTab, MatTabContent, MatTabGroup} from '@angular/material/tabs'; +import {MatToolbar} from '@angular/material/toolbar'; import {PlainObject} from '@angular-ru/cdk/typings'; +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; import {hlJsCode} from '../../../../../.global/utils/hljs-code'; import {MocksGenerator} from '../../mocks-generator'; @Component({ selector: 'sample-seven', + imports: [MatButton, MatTab, MatTabContent, MatTabGroup, MatToolbar, VirtualTable], templateUrl: './sample-seven.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SampleSevenComponent implements OnInit, AfterViewInit { - public data: PlainObject[] = []; - - constructor(private readonly cd: ChangeDetectorRef) {} +export default class SampleSevenComponent implements OnInit, AfterViewInit { + public data = signal([]); public ngOnInit(): void { const rowsNumber = 10000; const cols = 30; MocksGenerator.generator(rowsNumber, cols).then((data: PlainObject[]): void => { - this.data = data; - this.cd.detectChanges(); + this.data.set(data); }); } diff --git a/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.module.ts b/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.module.ts deleted file mode 100644 index 688b6d79e..000000000 --- a/apps/virtual-table-demo/src/samples/sample-seven/sample-seven.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -import {SharedModule} from '../../shared/shared.module'; -import {SampleSevenComponent} from './sample-seven.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild([{path: '', component: SampleSevenComponent}]), - ], - declarations: [SampleSevenComponent], -}) -export class SampleSevenModule {} diff --git a/apps/virtual-table-demo/src/samples/sample-seventeen/sample-seventeen.component.ts b/apps/virtual-table-demo/src/samples/sample-seventeen/sample-seventeen.component.ts index 36ac5b6d2..0cdcc8699 100644 --- a/apps/virtual-table-demo/src/samples/sample-seventeen/sample-seventeen.component.ts +++ b/apps/virtual-table-demo/src/samples/sample-seventeen/sample-seventeen.component.ts @@ -1,5 +1,7 @@ import {ChangeDetectionStrategy, Component} from '@angular/core'; -import {TableUpdateSchema} from '@angular-ru/cdk/virtual-table'; +import {MatToolbar} from '@angular/material/toolbar'; +import type {TableUpdateSchema} from '@angular-ru/cdk/virtual-table'; +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; interface LicenseSample { id: number; @@ -10,10 +12,11 @@ interface LicenseSample { @Component({ selector: 'sample-seventeen', + imports: [MatToolbar, VirtualTable], templateUrl: './sample-seventeen.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SampleSeventeenComponent { +export default class SampleSeventeenComponent { public invalidCachedSchema: TableUpdateSchema = { name: 'hello', version: 1, diff --git a/apps/virtual-table-demo/src/samples/sample-seventeen/sample-seventeen.module.ts b/apps/virtual-table-demo/src/samples/sample-seventeen/sample-seventeen.module.ts deleted file mode 100644 index b8f44d668..000000000 --- a/apps/virtual-table-demo/src/samples/sample-seventeen/sample-seventeen.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -import {SharedModule} from '../../shared/shared.module'; -import {SampleSeventeenComponent} from './sample-seventeen.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild([{path: '', component: SampleSeventeenComponent}]), - ], - declarations: [SampleSeventeenComponent], -}) -export class SampleSeventeenModule {} diff --git a/apps/virtual-table-demo/src/samples/sample-six/sample-six.component.html b/apps/virtual-table-demo/src/samples/sample-six/sample-six.component.html index 28dda3860..0b67ffd16 100644 --- a/apps/virtual-table-demo/src/samples/sample-six/sample-six.component.html +++ b/apps/virtual-table-demo/src/samples/sample-six/sample-six.component.html @@ -4,6 +4,7 @@ @@ -14,7 +16,7 @@ [name]="testName" [schema-columns]="schema" [schema-version]="1" - [source]="data" + [source]="data()" [vertical-border]="false" > Column list - - {{ column.key }} - - - - + {{ column.key }} + + + + + } - + @for (key of table.modelColumnKeys; track key) { + + } - + diff --git a/apps/virtual-table-demo/src/samples/sample-sixteen/sample-sixteen.component.ts b/apps/virtual-table-demo/src/samples/sample-sixteen/sample-sixteen.component.ts index b49685559..dbe56d5df 100644 --- a/apps/virtual-table-demo/src/samples/sample-sixteen/sample-sixteen.component.ts +++ b/apps/virtual-table-demo/src/samples/sample-sixteen/sample-sixteen.component.ts @@ -1,39 +1,40 @@ import { AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, + inject, OnInit, + signal, } from '@angular/core'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {MatButton} from '@angular/material/button'; +import {MatCheckbox} from '@angular/material/checkbox'; import {MatDialog} from '@angular/material/dialog'; +import {MatIcon} from '@angular/material/icon'; +import {MatToolbar} from '@angular/material/toolbar'; import {Nullable, PlainObject} from '@angular-ru/cdk/typings'; -import { - NgxTableViewChangesService, - TableUpdateSchema, -} from '@angular-ru/cdk/virtual-table'; -import {Subject} from 'rxjs'; -import {takeUntil} from 'rxjs/operators'; +import type {TableUpdateSchema} from '@angular-ru/cdk/virtual-table'; +import {NgxTableViewChangesService, VirtualTable} from '@angular-ru/cdk/virtual-table'; import {hlJsCode} from '../../../../../.global/utils/hljs-code'; import {MocksGenerator} from '../../mocks-generator'; +import {CodeDialogComponent} from '../../shared/dialog/code-dialog.component'; @Component({ selector: 'sample-sixteen', + imports: [MatButton, MatCheckbox, MatIcon, MatToolbar, VirtualTable], templateUrl: './sample-sixteen.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SampleSixteenComponent implements OnInit, AfterViewInit, OnDestroy { - private readonly destroy$ = new Subject(); - public data: PlainObject[] = []; - public schema: Nullable = null; - public readonly testName: string = 'test'; +export default class SampleSixteenComponent implements OnInit, AfterViewInit { + private readonly dialog = inject(MatDialog); + private readonly tableChanges = inject(NgxTableViewChangesService); + private readonly destroyRef = inject(DestroyRef); - constructor( - public readonly dialog: MatDialog, - private readonly cd: ChangeDetectorRef, - private readonly tableChanges: NgxTableViewChangesService, - ) {} + public data = signal([]); + public schema: Nullable = null; + public readonly testName = 'test'; public ngOnInit(): void { this.schema = JSON.parse( @@ -44,13 +45,12 @@ export class SampleSixteenComponent implements OnInit, AfterViewInit, OnDestroy MocksGenerator.generator(rowNumber, colsNumber).then( (data: PlainObject[]): void => { - this.data = data; - this.cd.detectChanges(); + this.data.set(data); }, ); this.tableChanges.events$ - .pipe(takeUntil(this.destroy$)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((event: TableUpdateSchema): void => this.save(event)); } @@ -58,14 +58,41 @@ export class SampleSixteenComponent implements OnInit, AfterViewInit, OnDestroy hlJsCode(); } - public ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - private save(event: TableUpdateSchema): void { // eslint-disable-next-line no-console console.log('update schema', event); window.localStorage.setItem(this.testName, JSON.stringify(event)); } + + protected showSample() { + this.dialog.open(CodeDialogComponent, { + data: { + title: 'Overview persistent state table', + description: '', + language: 'typescript', + code: ` +export class MyComponent implements OnInit { + protected schema: Nullable = null; + private readonly tableChanges = inject(NgxTableViewChangesService); + private readonly testName = 'test'; + private readonly destroyRef = inject(DestroyRef); + + ngOnInit() { + this.schema = JSON.parse( + window.localStorage.getItem(this.testName) ?? '{}', + ) as TableUpdateSchema; + + this.tableChanges.events$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((event: TableUpdateSchema) => this.save(event)); + } + + private save(event: TableUpdateSchema) { + window.localStorage.setItem(this.testName, JSON.stringify(event)); + } +} + `, + }, + }); + } } diff --git a/apps/virtual-table-demo/src/samples/sample-sixteen/sample-sixteen.module.ts b/apps/virtual-table-demo/src/samples/sample-sixteen/sample-sixteen.module.ts deleted file mode 100644 index dcb1a9ca2..000000000 --- a/apps/virtual-table-demo/src/samples/sample-sixteen/sample-sixteen.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -import {SharedModule} from '../../shared/shared.module'; -import {SampleSixteenComponent} from './sample-sixteen.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild([{path: '', component: SampleSixteenComponent}]), - ], - declarations: [SampleSixteenComponent], -}) -export class SampleSixteenModule {} diff --git a/apps/virtual-table-demo/src/samples/sample-third/sample-third.component.html b/apps/virtual-table-demo/src/samples/sample-third/sample-third.component.html index bfac8b204..b18768eb4 100644 --- a/apps/virtual-table-demo/src/samples/sample-third/sample-third.component.html +++ b/apps/virtual-table-demo/src/samples/sample-third/sample-third.component.html @@ -1,11 +1,14 @@ Example selection - ({{ table.source?.length }}x{{ table.displayedColumns.length }}) + @if (table.isRendered) { + ({{ table.source()?.length }}x{{ table.displayedColumns.length }}) + } Selected: {{ table.selection.selectionModel.size }}
diff --git a/apps/virtual-table-demo/src/samples/sample-thirteen/sample-thirteen.component.ts b/apps/virtual-table-demo/src/samples/sample-thirteen/sample-thirteen.component.ts index 0e791b2cd..a3b0d0a98 100644 --- a/apps/virtual-table-demo/src/samples/sample-thirteen/sample-thirteen.component.ts +++ b/apps/virtual-table-demo/src/samples/sample-thirteen/sample-thirteen.component.ts @@ -2,22 +2,28 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, + inject, ViewEncapsulation, } from '@angular/core'; +import {MatCheckbox} from '@angular/material/checkbox'; +import {MatToolbar} from '@angular/material/toolbar'; import {PlainObject} from '@angular-ru/cdk/typings'; -import {TableEvent} from '@angular-ru/cdk/virtual-table'; +import {TableEvent, VirtualTable} from '@angular-ru/cdk/virtual-table'; import {ToastrService} from 'ngx-toastr'; import {hlJsCode} from '../../../../../.global/utils/hljs-code'; @Component({ selector: 'sample-thirteen', + imports: [MatCheckbox, MatToolbar, VirtualTable], templateUrl: './sample-thirteen.component.html', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [ToastrService], }) -export class SampleThirteenComponent implements AfterViewInit { +export default class SampleThirteenComponent implements AfterViewInit { + private readonly toast = inject(ToastrService); + public data: PlainObject[] = [ { id: 1, @@ -41,8 +47,6 @@ export class SampleThirteenComponent implements AfterViewInit { }, ]; - constructor(private readonly toast: ToastrService) {} - public ngAfterViewInit(): void { this.update(); } diff --git a/apps/virtual-table-demo/src/samples/sample-thirteen/sample-thirteen.module.ts b/apps/virtual-table-demo/src/samples/sample-thirteen/sample-thirteen.module.ts deleted file mode 100644 index 0faaea1d5..000000000 --- a/apps/virtual-table-demo/src/samples/sample-thirteen/sample-thirteen.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; -import {ToastrModule} from 'ngx-toastr'; - -import {SharedModule} from '../../shared/shared.module'; -import {SampleThirteenComponent} from './sample-thirteen.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - ToastrModule, - RouterModule.forChild([{path: '', component: SampleThirteenComponent}]), - ], - declarations: [SampleThirteenComponent], -}) -export class SampleThirteenModule {} diff --git a/apps/virtual-table-demo/src/samples/sample-twelve/sample-twelve.component.html b/apps/virtual-table-demo/src/samples/sample-twelve/sample-twelve.component.html index 5b2316512..fcdfc7931 100644 --- a/apps/virtual-table-demo/src/samples/sample-twelve/sample-twelve.component.html +++ b/apps/virtual-table-demo/src/samples/sample-twelve/sample-twelve.component.html @@ -85,7 +85,7 @@

font-size: 16px; font-weight: bold; margin-bottom: 10px; - { + }

@@ -148,7 +148,7 @@

Header and footer are always sticky position

Table with header can be expandable
([]); public licences: PlainObject[] = [ { @@ -43,16 +46,13 @@ export class SampleTwelveComponent implements OnInit, AfterViewInit { }, ]; - constructor(private readonly cd: ChangeDetectorRef) {} - public ngOnInit(): void { const rowNumber = 50; const colsNumber = 15; MocksGenerator.generator(rowNumber, colsNumber).then( (data: PlainObject[]): void => { - this.data = data; - this.cd.detectChanges(); + this.data.set(data); }, ); } diff --git a/apps/virtual-table-demo/src/samples/sample-twelve/sample-twelve.module.ts b/apps/virtual-table-demo/src/samples/sample-twelve/sample-twelve.module.ts deleted file mode 100644 index c9154d35a..000000000 --- a/apps/virtual-table-demo/src/samples/sample-twelve/sample-twelve.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {RouterModule} from '@angular/router'; - -import {SharedModule} from '../../shared/shared.module'; -import {SampleTwelveComponent} from './sample-twelve.component'; - -@NgModule({ - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild([{path: '', component: SampleTwelveComponent}]), - ], - declarations: [SampleTwelveComponent], -}) -export class SampleTwelveModule {} diff --git a/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.component.ts b/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.component.ts index a0079fe56..35723628a 100644 --- a/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.component.ts +++ b/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.component.ts @@ -1,24 +1,53 @@ -import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core'; -import {FormBuilder, FormControl, FormGroup} from '@angular/forms'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {KeyValuePipe} from '@angular/common'; +import {ChangeDetectionStrategy, Component, inject, OnInit} from '@angular/core'; +import { + FormBuilder, + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import {MatButton} from '@angular/material/button'; +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogContent, + MatDialogRef, + MatDialogTitle, +} from '@angular/material/dialog'; +import {MatFormField, MatInput, MatLabel} from '@angular/material/input'; import {Nullable} from '@angular-ru/cdk/typings'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type AnyType = any; - @Component({ selector: 'dialog-template', + imports: [ + FormsModule, + KeyValuePipe, + MatButton, + MatDialogActions, + MatDialogContent, + MatDialogTitle, + MatFormField, + MatInput, + MatLabel, + ReactiveFormsModule, + ], templateUrl: './dialog-template.template.html', + styles: ` + form { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; + } + `, changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogTemplateComponent implements OnInit { - public form: Nullable = null; + public data = inject(MAT_DIALOG_DATA); + public dialogRef = inject>(MatDialogRef); + private readonly fb = inject(FormBuilder); - constructor( - @Inject(MAT_DIALOG_DATA) public data: AnyType, - public dialogRef: MatDialogRef, - private readonly fb: FormBuilder, - ) {} + public form: Nullable = null; public ngOnInit(): void { this.form = this.fb.group({ diff --git a/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.template.html b/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.template.html index c8a953f0b..9bc726692 100644 --- a/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.template.html +++ b/apps/virtual-table-demo/src/shared/dialog-template/dialog-template.template.html @@ -1,22 +1,28 @@ -

Edit

+

Edit

- - - - - + + @if (form) { +
+ @for (input of data | keyvalue; track input.key) { + + {{ input.key }} + + + } +
+ } +
- + + + diff --git a/apps/virtual-table-demo/src/shared/dialog/code-dialog.component.ts b/apps/virtual-table-demo/src/shared/dialog/code-dialog.component.ts index a15defd65..fd4432a0e 100644 --- a/apps/virtual-table-demo/src/shared/dialog/code-dialog.component.ts +++ b/apps/virtual-table-demo/src/shared/dialog/code-dialog.component.ts @@ -1,25 +1,31 @@ -import {AfterViewInit, ChangeDetectionStrategy, Component, Inject} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {AfterViewInit, ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {MatButton} from '@angular/material/button'; +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogClose, + MatDialogContent, + MatDialogTitle, +} from '@angular/material/dialog'; import {hlJsCode} from '../../../../../.global/utils/hljs-code'; @Component({ selector: 'code-dialog', + imports: [ + MatButton, + MatDialogActions, + MatDialogClose, + MatDialogContent, + MatDialogTitle, + ], templateUrl: './code-dialog.template.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class CodeDialogComponent implements AfterViewInit { - constructor( - @Inject(MAT_DIALOG_DATA) public data: any | unknown, - @Inject(MatDialogRef) - public dialogRef: MatDialogRef, - ) {} + public data = inject(MAT_DIALOG_DATA); public ngAfterViewInit(): void { hlJsCode(); } - - public close(): void { - this.dialogRef.close(); - } } diff --git a/apps/virtual-table-demo/src/shared/dialog/code-dialog.template.html b/apps/virtual-table-demo/src/shared/dialog/code-dialog.template.html index 87d303bb9..1bc6f5288 100644 --- a/apps/virtual-table-demo/src/shared/dialog/code-dialog.template.html +++ b/apps/virtual-table-demo/src/shared/dialog/code-dialog.template.html @@ -1,14 +1,19 @@ -

{{ data.title }}

-

-
{{ data.code }}
+

{{ data.title }}

+ + @if (data.description) { +

+ } +
{{ data.code }}
+
- + + + diff --git a/apps/virtual-table-demo/src/shared/shared.module.ts b/apps/virtual-table-demo/src/shared/shared.module.ts deleted file mode 100644 index ecf03ec43..000000000 --- a/apps/virtual-table-demo/src/shared/shared.module.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {DragDropModule} from '@angular/cdk/drag-drop'; -import {ScrollingModule} from '@angular/cdk/scrolling'; -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {MatButtonModule} from '@angular/material/button'; -import {MatCardModule} from '@angular/material/card'; -import {MatCheckboxModule} from '@angular/material/checkbox'; -import {MatDialogModule} from '@angular/material/dialog'; -import {MatDividerModule} from '@angular/material/divider'; -import {MatIconModule} from '@angular/material/icon'; -import {MatInputModule} from '@angular/material/input'; -import {MatListModule} from '@angular/material/list'; -import {MatMenuModule} from '@angular/material/menu'; -import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; -import {MatSelectModule} from '@angular/material/select'; -import {MatSidenavModule} from '@angular/material/sidenav'; -import {MatSnackBarModule} from '@angular/material/snack-bar'; -import {MatTableModule} from '@angular/material/table'; -import {MatTabsModule} from '@angular/material/tabs'; -import {MatToolbarModule} from '@angular/material/toolbar'; -import {TableBuilderModule} from '@angular-ru/cdk/virtual-table'; - -import {CodeDialogComponent} from './dialog/code-dialog.component'; -import {DialogTemplateComponent} from './dialog-template/dialog-template.component'; - -@NgModule({ - imports: [ - TableBuilderModule.forRoot(), - CommonModule, - FormsModule, - MatButtonModule, - MatInputModule, - MatMenuModule, - ReactiveFormsModule, - ], - declarations: [CodeDialogComponent, DialogTemplateComponent], - exports: [ - CodeDialogComponent, - DragDropModule, - FormsModule, - MatButtonModule, - MatCardModule, - MatCheckboxModule, - MatDialogModule, - MatDividerModule, - MatIconModule, - MatInputModule, - MatListModule, - MatProgressSpinnerModule, - MatSelectModule, - MatSidenavModule, - MatSnackBarModule, - MatTableModule, - MatTabsModule, - MatToolbarModule, - ScrollingModule, - TableBuilderModule, - MatMenuModule, - ], -}) -export class SharedModule {} diff --git a/apps/virtual-table-demo/styles.scss b/apps/virtual-table-demo/styles.scss index 19580bacc..6cf678218 100644 --- a/apps/virtual-table-demo/styles.scss +++ b/apps/virtual-table-demo/styles.scss @@ -31,26 +31,31 @@ sample-eight ngx-table-builder * { } .my-filter { - padding: 10px 5px 10px 5px; -} - -.my-filter .mat-form-field { - width: 100%; + padding: 10px; } .my-filter .filter-form { display: flex; + gap: 1rem; + + mat-form-field { + width: 100%; + + &.filter-options { + width: 25%; + } + } } .my-filter .filter-options.mat-form-field { margin-right: 0; } -.my-filter .filter-options { - width: 40%; -} - .my-filter .mat-form-field-appearance-outline .mat-form-field-wrapper { margin-bottom: 0; padding: 2px 0; } + +mat-form-field.mat-mdc-form-field { + padding-top: 0.25rem; +} diff --git a/apps/virtual-table-demo/tsconfig.app.json b/apps/virtual-table-demo/tsconfig.app.json index 5ede5f85d..64c8041c9 100644 --- a/apps/virtual-table-demo/tsconfig.app.json +++ b/apps/virtual-table-demo/tsconfig.app.json @@ -1,4 +1,10 @@ { "extends": "../../tsconfig.json", - "include": ["main.ts", "polyfills.ts"] + "compilerOptions": { + "types": [] + }, + "include": ["main.ts"], + "angularCompilerOptions": { + "compilationMode": "full" + } } diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index badc04c49..da27b90ab 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -2,6 +2,7 @@ - Getting Started - [Introduction](introduction/intro.md) + - [Migration Guide](introduction/migration-v19.md) - CDK - [@angular-ru/cdk/typings](cdk/typings.md) - [@angular-ru/cdk/number](cdk/number.md) diff --git a/docs/cdk/date-suggestion.md b/docs/cdk/date-suggestion.md index e201857ae..2a8465bcb 100644 --- a/docs/cdk/date-suggestion.md +++ b/docs/cdk/date-suggestion.md @@ -1,8 +1,9 @@ #### `@angular-ru/cdk/date` -#### DateSuggestionModule +#### DateSuggestion Composer -Module for convenient substitution of date intervals depending on values and the current date. +DateSuggestion composer can be used for convenient substitution of date intervals depending on values and the current +date. ##### Default usage @@ -17,16 +18,23 @@ Default implementation has 8 built-in strategies: - `LAST_180_DAYS_OF_INTERVAL` — set 180 days to the last day from the current interval. ```typescript -import {DateSuggestionModule, DateSuggestionComposer, DefaultDateIntervalSuggestion} from '@angular-ru/cdk/date'; +import { + DateSuggestionComposer, + DayOfWeek, + DefaultDateIntervalSuggestion, + FIRST_DAY_OF_WEEK, + provideDateSuggestion, +} from '@angular-ru/cdk/date'; -@NgModule({ - // ... - imports: [ - // ... - DateSuggestionModule, +export const appConfig: ApplicationConfig = { + providers: [ + provideDateSuggestion(), + { + provide: FIRST_DAY_OF_WEEK, + useValue: DayOfWeek.Monday, + }, ], -}) -export class AppModule {} +}; @Component({ // ... @@ -68,10 +76,10 @@ import { DAYS_COUNT, DEFAULT_SUGGESTION_STRATEGY_MAP, SuggestionStrategyMap, - DateSuggestionModule, DateSuggestionComposer, DefaultDateIntervalSuggestion, endOfDay, + provideDateSuggestion, shiftDate, startOfDay, } from '@angular-ru/cdk/date'; @@ -107,15 +115,9 @@ const EXTENDED_STRATEGY_MAP: SuggestionStrategyMap @@ -202,20 +166,15 @@ class TestComponent { } ``` -- `InputFilterDirective, InputFilterModule` +- `InputFilter` ```typescript -import {InputFilterModule, FilterPredicate} from '@angular-ru/cdk/directives'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [InputFilterModule], -}) -export class AppModule {} +import {InputFilter, FilterPredicate} from '@angular-ru/cdk/directives'; +import {Component} from '@angular/core'; @Component({ //... + imports: [InputFilter], template: `
for example, limitConcurrency: 5
This mean that maximum of 5 requests can be executed in parallel. Next one immediately start only if one of the previous requests is completed -- `app.module.ts` +- `app.config.ts` ```typescript -import {DataHttpClientModule} from '@angular-ru/cdk/http'; +import {provideHttpClient} from '@angular/common/http'; +import {provideDataHttpClientOptions} from '@angular-ru/cdk/http'; -@NgModule({ - imports: [ - // ... - DataHttpClientModule.forRoot([ApiUsersClient], { +export const appConfig: ApplicationConfig = { + providers: [ + provideHttpClient(), + provideDataHttpClientOptions([ApiUsersClient], { // ... limitConcurrency: 5, }), ], - // ... -}) -export class AppModule {} +}; ``` ![](../assets/limit-concurrency-5.png) diff --git a/docs/cdk/logger.md b/docs/cdk/logger.md index bda796948..fc58a15a9 100644 --- a/docs/cdk/logger.md +++ b/docs/cdk/logger.md @@ -3,16 +3,11 @@ > Lightweight and configurable Angular logger ```typescript -import { LoggerModule } from '@angular-ru/cdk/logger'; -... - -@NgModule({ - imports: [ - LoggerModule.forRoot() - ], - ... -}) -export class AppModule {} +import {provideLogger} from '@angular-ru/cdk/logger'; + +export const appConfig: ApplicationConfig = { + providers: [provideLogger()], +}; ``` ## Motivation @@ -46,19 +41,14 @@ $ npm install @angular-ru/cdk --save ``` ```typescript -import { LoggerModule } from '@angular-ru/cdk/logger'; -... - -@NgModule({ - imports: [ - LoggerModule.forRoot() - ], - ... -}) -export class AppModule {} +import {provideLogger} from '@angular-ru/cdk/logger'; + +export const appConfig: ApplicationConfig = { + providers: [provideLogger()], +}; ``` -**Online demo**: https://angular-ru.github.io/angular-ru-logger-example-app/ +[**Online demo**](https://angular-ru.github.io/angular-ru-logger-example-app) ![](https://habrastorage.org/webt/lq/a9/_s/lqa9_sp8gxkwax_sy6x9w3qf5ry.gif) @@ -338,26 +328,24 @@ export class AppComponent implements OnInit { ### Example: CSS classes ```typescript -import { LoggerModule } from '@angular-ru/cdk/logger'; - -@NgModule({ - // .. - imports: [ - LoggerModule.forRoot({ - cssClassMap: { - bold: 'font-weight: bold', - 'line-through': 'text-decoration: line-through', - 'code-sandbox': ` +import {provideLogger} from '@angular-ru/cdk/logger'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideLogger({ + cssClassMap: { + bold: 'font-weight: bold', + 'line-through': 'text-decoration: line-through', + 'code-sandbox': ` color: #666; background: #f4f4f4; border-left: 3px solid #f36d33; font-family: monospace; - font-size: 15px;` - } - }) - ] - // .. -}) + font-size: 15px;`, + }, + }), + ], +}; ``` ```typescript @@ -376,7 +364,6 @@ export class AppComponent implements OnInit { this.logger.cssClass('bold line-through').debug('JavaScript sucks', 'JavaScript is the best'); } } -export class AppModule {} ``` ![](https://habrastorage.org/webt/d5/tm/aa/d5tmaaomjql5px_wkzxnodhacnk.png) @@ -510,12 +497,11 @@ export class AppComponent { ### Example: format output ```typescript -import {LoggerModule, NgModule, FormatOutput} from '@angular-ru/cdk/logger'; +import {FormatOutput, provideLogger} from '@angular-ru/cdk/logger'; -@NgModule({ - //.. - imports: [ - LoggerModule.forRoot({ +export const appConfig: ApplicationConfig = { + providers: [ + provideLogger({ format(label: string, labelStyle: string): FormatOutput { const date = new Date().toLocaleString('ru-RU').replace(',', ''); const customLabel: string = `${date} ${label}`; @@ -523,8 +509,7 @@ import {LoggerModule, NgModule, FormatOutput} from '@angular-ru/cdk/logger'; }, }), ], -}) -export class AppModule {} +}; ``` ```typescript @@ -548,12 +533,11 @@ export class AppComponent implements OnInit { ### Example: full configurations ```typescript -import {LoggerModule, NgModule, LoggerLevel} from '@angular-ru/cdk/logger'; +import {LoggerLevel, provideLogger} from '@angular-ru/cdk/logger'; -@NgModule({ - // .. - imports: [ - LoggerModule.forRoot({ +export const appConfig: ApplicationConfig = { + providers: [ + provideLogger({ useLevelGroup: true, globalLineStyle: 'color: red; text-decoration: underline; font-weight: bold; font-size: 15px', cssClassMap: { @@ -582,27 +566,23 @@ import {LoggerModule, NgModule, LoggerLevel} from '@angular-ru/cdk/logger'; }, }), ], - // .. -}) -export class AppModule {} +}; ``` ```typescript -import { LoggerService } from '@angular-ru/cdk/logger'; +import {LoggerService} from '@angular-ru/cdk/logger'; -export class AppComponent implements OnInit { +export class AppComponent { + constructor(private readonly logger: LoggerService) {} - public ngOnInit(): void { - constructor(private readonly logger: LoggerService) {} - - public showExample(): void { - this.logger.log('Example'); - this.logger.trace('trace is worked', 1, { a: 1 }); - this.logger.debug('debug is worked', 2, console); - this.logger.info('info is worked', 3, Object); - this.logger.warn('warn is worked', 4, String); - this.logger.error('error is worked', 5, (2.55).toFixed()); - } + public showExample(): void { + this.logger.log('Example'); + this.logger.trace('trace is worked', 1, {a: 1}); + this.logger.debug('debug is worked', 2, console); + this.logger.info('info is worked', 3, Object); + this.logger.warn('warn is worked', 4, String); + this.logger.error('error is worked', 5, (2.55).toFixed()); + } } ``` diff --git a/docs/cdk/pipes.md b/docs/cdk/pipes.md index 7720179fb..06273a128 100644 --- a/docs/cdk/pipes.md +++ b/docs/cdk/pipes.md @@ -1,21 +1,16 @@ #### `@angular-ru/cdk/pipes` -- `MutableTypePipe, MutableTypePipeModule` +- `MutableTypePipe` ```typescript -import {MutableTypePipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; +import {MutableTypePipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; import {Immutable} from '@angular-ru/typings'; import {Data} from './data'; -@NgModule({ - // .. - imports: [MutableTypePipeModule], -}) -export class AppModule {} - @Component({ //... + imports: [MutableTypePipe], template: ` `, @@ -38,20 +33,15 @@ expect(obj.a).toEqual('str2'); expect(mutableObj.a).toEqual('str2'); ``` -- `DeepPathPipe, DeepPathPipeModule` +- `DeepPathPipe` ```typescript -import {DeepPathPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [DeepPathPipeModule], -}) -export class AppModule {} +import {DeepPathPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [DeepPathPipe], template: ` {{ data | deepPath: 'a.b.c' }} `, // view: 'hello' @@ -61,20 +51,15 @@ export class AppComponent { } ``` -- `DefaultValuePipe, DefaultValuePipeModule` +- `DefaultValuePipe` ```typescript -import {DefaultValuePipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [DefaultValuePipeModule], -}) -export class AppModule {} +import {DefaultValuePipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [DefaultValuePipe], template: ` {{ data | defaultValue: '-' }} `, // view: '-' @@ -84,20 +69,15 @@ export class AppComponent { } ``` -- `IsNotNullPipe, IsNotNullPipeModule` +- `IsNotNullPipe` ```typescript -import {IsNotNullPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [IsNotNullPipeModule], -}) -export class AppModule {} +import {IsNotNullPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [IsNotNullPipe], template: ` {{ data | isNotNull }} `, // false @@ -107,20 +87,15 @@ export class AppComponent { } ``` -- `IsNilPipe, IsNilPipeModule` +- `IsNilPipe` ```typescript -import {IsNilPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [IsNilPipeModule], -}) -export class AppModule {} +import {IsNilPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [IsNilPipe], template: ` {{ data | isNil }} `, // true @@ -130,20 +105,15 @@ export class AppComponent { } ``` -- `IsObjectPipe, IsObjectPipeModule` +- `IsObjectPipe` ```typescript -import {IsObjectPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [IsObjectPipeModule], -}) -export class AppModule {} +import {IsObjectPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [IsObjectPipe], template: ` {{ data | isObject }} `, // true @@ -153,20 +123,15 @@ export class AppComponent { } ``` -- `DetectBrowserPipe, DetectBrowserPipeModule` +- `DetectBrowserPipe` ```typescript -import {DetectBrowserPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [DetectBrowserPipeModule], -}) -export class AppModule {} +import {DetectBrowserPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [DetectBrowserPipe], template: ` {{ browser | detectBrowser }} `, // Chrome 84 @@ -177,20 +142,15 @@ export class AppComponent { } ``` -- `IsStringPipe, IsStringPipeModule` +- `IsStringPipe` ```typescript -import {IsStringPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [IsStringPipeModule], -}) -export class AppModule {} +import {IsStringPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [IsStringPipe], template: ` {{ data | isString }} `, // true @@ -200,20 +160,15 @@ export class AppComponent { } ``` -- `IsArrayPipe, IsArrayPipeModule` +- `IsArrayPipe` ```typescript -import {IsArrayPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [IsArrayPipeModule], -}) -export class AppModule {} +import {IsArrayPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [IsArrayPipe], template: ` {{ data | isArray }} `, // true @@ -223,20 +178,15 @@ export class AppComponent { } ``` -- `ToStringPipe, ToStringPipeModule` +- `ToStringPipe` ```typescript -import {ToStringPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [ToStringPipeModule], -}) -export class AppModule {} +import {ToStringPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [ToStringPipe], template: ` {{ data | toString }} `, // "1,2" @@ -246,20 +196,15 @@ export class AppComponent { } ``` -- `ToNumberPipe, ToNumberPipeModule` +- `ToNumberPipe` ```typescript -import {ToNumberPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [ToNumberPipeModule], -}) -export class AppModule {} +import {ToNumberPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [ToNumberPipe], template: ` {{ data | toNumber }} `, // 12 @@ -269,20 +214,15 @@ export class AppComponent { } ``` -- `FormatDatePipe, FormatDatePipeModule` +- `FormatDatePipe` ```typescript -import {FormatDatePipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [FormatDatePipeModule], -}) -export class AppModule {} +import {FormatDatePipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [FormatDatePipe], template: ` {{ data | formatDate }} `, // 11.12.2018 @@ -292,20 +232,15 @@ export class AppComponent { } ``` -- `SafePipe, SafePipeModule` +- `SafePipe` ```typescript -import {SafePipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [SafePipeModule], -}) -export class AppModule {} +import {SafePipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [SafePipe], template: `
`, @@ -315,20 +250,15 @@ export class AppComponent { } ``` -- `NumberFormatPipe, NumberFormatPipeModule` +- `NumberFormatPipe` ```typescript -import {NumberFormatPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [NumberFormatPipeModule], -}) -export class AppModule {} +import {NumberFormatPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [NumberFormatPipe], template: ` {{ data | numberFormat }} `, // 1 500 300,5 @@ -338,20 +268,15 @@ export class AppComponent { } ``` -- `HttpReplacerPipe, HttpReplacerPipeModule` +- `HttpReplacerPipe` ```typescript -import {HttpReplacerPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [HttpReplacerPipeModule], -}) -export class AppModule {} +import {HttpReplacerPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [HttpReplacerPipe], template: ` {{ data | httpReplacer }} `, // hello.com/new @@ -361,20 +286,15 @@ export class AppComponent { } ``` -- `TakeFirstItemPipe, TakeFirstItemPipeModule` +- `TakeFirstItemPipe` ```typescript -import {TakeFirstItemPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [TakeFirstItemPipeModule], -}) -export class AppModule {} +import {TakeFirstItemPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [TakeFirstItemPipe], template: ` {{ data | takeFirstItem }} `, // 1 @@ -384,20 +304,15 @@ export class AppComponent { } ``` -- `TakeSecondItemPipe, TakeSecondItemPipeModule` +- `TakeSecondItemPipe` ```typescript -import {TakeSecondItemPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [TakeSecondItemPipeModule], -}) -export class AppModule {} +import {TakeSecondItemPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [TakeSecondItemPipe], template: ` {{ data | takeSecondItem }} `, // 2 @@ -407,20 +322,15 @@ export class AppComponent { } ``` -- `DateToNativePipe, DateToNativePipeModule` +- `DateToNativePipe` ```typescript -import {DateToNativePipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [DateToNativePipeModule], -}) -export class AppModule {} +import {DateToNativePipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [DateToNativePipe], template: ` {{ data | dateNative }} `, // Date(type) @@ -430,20 +340,15 @@ export class AppComponent { } ``` -- `EntrySingleSetPipe, EntrySingleSetPipeModule` +- `EntrySingleSetPipe` ```typescript -import {EntrySingleSetPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [EntrySingleSetPipeModule], -}) -export class AppModule {} +import {EntrySingleSetPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [EntrySingleSetPipe], template: ` {{ 'a' | entrySingleSet: setList }} `, // true @@ -453,20 +358,15 @@ export class AppComponent { } ``` -- `MarkByFilterPipe, MarkByFilterPipeModuleModule` +- `MarkByFilterPipe` ```typescript -import {MarkByFilterPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [MarkByFilterPipeModule], -}) -export class AppModule {} +import {MarkByFilterPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [MarkByFilterPipe], template: ` {{ 'hello word' | markByFilter: filter }} `, // hello world @@ -476,20 +376,15 @@ export class AppComponent { } ``` -- `DisplayItemPipe, DisplayItemPipeModule` +- `DisplayItemPipe` ```typescript -import {DisplayItemPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // ... - imports: [DisplayItemPipeModule], -}) -export class AppModule {} +import {DisplayItemPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ - // ... + //... + imports: [DisplayItemPipe], template: ` {{ entity | displayItem: 'value.name' }} `, // A @@ -499,20 +394,15 @@ export class AppComponent { } ``` -- `ObjectSizePipe, ObjectSizePipeModule` +- `ObjectSizePipe` ```typescript -import {ObjectSizePipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [ObjectSizePipeModule], -}) -export class AppModule {} +import {ObjectSizePipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [ObjectSizePipe], template: ` {{ [{a: 1}, {a: 2}] | objectSize }} @@ -524,20 +414,15 @@ export class AppModule {} export class AppComponent {} ``` -- `MergeCssClassesPipe, MergeCssClassesPipeModule` +- `MergeCssClassesPipe` ```typescript import {MergeCssClassesPipe} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [MergeCssClassesPipe], -}) -export class AppModule {} +import {Component} from '@angular/core'; @Component({ //... + imports: [MergeCssClassesPipe], template: `
@@ -580,17 +460,12 @@ export class AppComponent {} ``` ```typescript -import {JoinPipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [JoinPipeModule], -}) -export class AppModule {} +import {JoinPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [JoinPipe], template: `

@@ -600,17 +475,12 @@ export class AppComponent {} ``` ```typescript -import {JoinPipeModule, JoinMapTransformer} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [JoinPipeModule], -}) -export class AppModule {} +import {JoinPipe, JoinMapTransformer} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [JoinPipe], template: `

@@ -621,20 +491,15 @@ export class AppComponent { } ``` -- `BracePipe, BracePipeModule` +- `BracePipe` ```typescript -import {BracePipeModule} from '@angular-ru/cdk/pipes'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [BracePipeModule], -}) -export class AppModule {} +import {BracePipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [BracePipe], template: ` Edit selected records {{ count | brace }} @@ -645,21 +510,16 @@ export class AppComponent { } ``` -- `FilterUniquePipe, FilterUniquePipeModule` +- `FilterUniquePipe` ```typescript -import {FilterUniquePipeModule} from '@angular-ru/cdk/pipes'; +import {FilterUniquePipe} from '@angular-ru/cdk/pipes'; import {PlainObject} from '@angular-ru/cdk-typings'; -import {Component, NgModule} from '@angular/core'; - -@NgModule({ - // .. - imports: [FilterUniquePipeModule], -}) -export class AppModule {} +import {Component} from '@angular/core'; @Component({ //... + imports: [FilterUniquePipe], template: `
{{ objects | filterUnique: 'name' | json }}
@@ -674,21 +534,16 @@ export class AppComponent { } ``` -- `TypeAsPipe, TypeAsPipeModule` +- `TypeAsPipe` ```typescript -import {TypeAs} from '@angular-ru/cdk/pipes'; - -@NgModule({ - // .. - imports: [TypeAsPipeModule], -}) -export class AppModule {} +import {TypeAsPipe} from '@angular-ru/cdk/pipes'; type SomeType = {a: number}; @Component({ //... + imports: [TypeAsPipe], template: `

{{ typed.a }} @@ -704,19 +559,15 @@ export class AppComponent { } ``` -- `AtPipe, AtPipeModule` +- `AtPipe` ```typescript -import {AtPipeModule} from '@angular-ru/cdk/pipes'; - -@NgModule({ - // .. - imports: [AtPipeModule], -}) -export class AppModule {} +import {AtPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [AtPipe], template: `

{{ someArray | at: 0 }}

@@ -730,25 +581,19 @@ export class AppComponent { ``` - `HasItems, HasManyItems, HasNoItems, HasOneItem, HasAtMostOneItem` -- `HasItemsModule, HasManyItemsModule, HasNoItemsModule, HasOneItemModule, HasAtMostOneItemModule` ```typescript import { - HasItemsModule, - HasManyItemsModule, - HasNoItemsModule, - HasOneItemModule, - HasAtMostOneItemModule, + HasItemsPipe, + HasManyItemsPipe, + HasNoItemsPipe, + HasOneItemPipe, + HasAtMostOneItemPipe, } from '@angular-ru/cdk/pipes'; -@NgModule({ - // .. - imports: [HasItemsModule, HasManyItemsModule, HasNoItemsModule, HasOneItemModule, HasAtMostOneItemModule], -}) -export class AppModule {} - @Component({ //... + imports: [HasItemsPipe, HasManyItemsPipe, HasNoItemsPipe, HasOneItemPipe, HasAtMostOneItemPipe], template: `
@@ -762,19 +607,15 @@ export class AppComponent { } ``` -- `IncludesPipe, IncludesPipeModule` +- `IncludesPipe` ```typescript -import {IncludesPipeModule} from '@angular-ru/cdk/pipes'; - -@NgModule({ - // .. - imports: [IncludesPipeModule], -}) -export class AppModule {} +import {IncludesPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [IncludesPipe], template: `

first

@@ -788,19 +629,15 @@ export class AppComponent { } ``` -- `HasPipe, HasPipeModule` +- `HasPipe` ```typescript -import {HasPipeModule} from '@angular-ru/cdk/pipes'; - -@NgModule({ - // .. - imports: [HasPipeModule], -}) -export class AppModule {} +import {HasPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [HasPipe], template: `

first

@@ -814,19 +651,15 @@ export class AppComponent { } ``` -- `CoerceBooleanPipe, CoerceBooleanPipeModule` +- `CoerceBooleanPipe` ```typescript -import {CoerceBooleanPipeModule} from '@angular-ru/cdk/pipes'; - -@NgModule({ - // .. - imports: [CoerceBooleanPipeModule], -}) -export class AppModule {} +import {CoerceBooleanPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [CoerceBooleanPipe], template: ` first

`, @@ -836,19 +669,15 @@ export class AppComponent { } ``` -- `DeclinationOfNumberPipe, DeclinationOfNumberPipeModule` +- `DeclinationOfNumberPipe` ```typescript -import {DeclinationOfNumberPipeModule} from '@angular-ru/cdk/pipes'; - -@NgModule({ - // .. - imports: [DeclinationOfNumberPipeModule], -}) -export class AppModule {} +import {DeclinationOfNumberPipe} from '@angular-ru/cdk/pipes'; +import {Component} from '@angular/core'; @Component({ //... + imports: [DeclinationOfNumberPipe], template: ` {{ numberVal | declinationOfNumber: ['арбуз', 'арбуза', 'арбузов'] }} `, diff --git a/docs/cdk/tooltip.md b/docs/cdk/tooltip.md index 52eef3a54..d6f668a82 100644 --- a/docs/cdk/tooltip.md +++ b/docs/cdk/tooltip.md @@ -2,7 +2,7 @@ ## Introduction -Demo: https://angular-ru.github.io/angular-ru-tooltip-example-app/ +[**Demo**](https://angular-ru.github.io/angular-ru-tooltip-example-app) ```bash $ npm install @angular-ru/cdk/tooltip @@ -17,16 +17,11 @@ $ npm install @angular-ru/cdk/tooltip ``` ```typescript -import {TooltipModule} from '@angular-ru/cdk/tooltip'; -import {NgModule} from '@angular/core'; +import {provideTooltip} from '@angular-ru/cdk/tooltip'; -@NgModule({ - imports: [ - // ... - TooltipModule.forRoot(), - ], -}) -export class AppModule {} +export const appConfig: ApplicationConfig = { + providers: [provideTooltip()], +}; ``` #### Basic example diff --git a/docs/cdk/virtual-table.md b/docs/cdk/virtual-table.md index e0218f3c8..0d0ddb5c7 100644 --- a/docs/cdk/virtual-table.md +++ b/docs/cdk/virtual-table.md @@ -3,25 +3,21 @@ The Angular Table Builder includes a comprehensive set of ready-to-use features covering everything from paging, sorting, filtering, editing, and grouping to row and column virtualization, and accessibility support. -Demo: https://angular-ru.github.io/angular-ru-ng-table-builder-example-app/ +[**Demo**](https://angular-ru.github.io/angular-ru-ng-table-builder-example-app) ```bash $ npm install --save @angular-ru/cdk ``` After a few seconds of waiting, you should be good to go. Let's get to the actual coding! As a first step, let's add the -Angular table builder module to our app module (src/app.module.ts): +virtual table provider to our app config (src/app.config.ts): ```typescript -import {TableBuilderModule} from '@angular-ru/cdk/virtual-table'; +import {provideVirtualTable} from '@angular-ru/cdk/virtual-table'; -@NgModule({ - imports: [ - // ... - TableBuilderModule.forRoot(), - ], -}) -export class AppModule {} +export const appConfig: ApplicationConfig = { + providers: [provideVirtualTable()], +}; ``` ### Simple use @@ -31,9 +27,11 @@ Next, let's declare the basic grid configuration. Edit src/app.component.ts: ```typescript import {Component} from '@angular/core'; import {MyData} from './my-data.interface'; +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; @Component({ selector: 'app-root', + imports: [VirtualTable], template: ` `, @@ -59,9 +57,11 @@ acts as the core upon which anyone can build their own tailored data-table exper // app.component.ts import {Component} from '@angular/core'; import {LicenseSample} from './license.interface'; +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; @Component({ selector: 'app', + imports: [VirtualTable], templateUrl: './app.component.html', }) export class AppComponent { @@ -121,9 +121,11 @@ export class AppComponent { // app.component.ts import {AfterViewInit, Component, ViewChild} from '@angular/core'; import {LicenseSample} from './license.interface'; +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; @Component({ selector: 'app', + imports: [VirtualTable], templateUrl: './app.component.html', }) export class AppComponent implements AfterViewInit { diff --git a/docs/introduction/migration-v19.md b/docs/introduction/migration-v19.md new file mode 100644 index 000000000..9287d1aba --- /dev/null +++ b/docs/introduction/migration-v19.md @@ -0,0 +1,208 @@ +## `v19` Migration Guide + +`v19` of `@angular-ru/cdk` and `@angular-ru/ngxs` has been updated to utilize the latest features from Angular `v20`. +This includes standalone components, environment providers, component input signals, and more. Because of this, +migration is slightly more involved than usual. + +### Standalone components, directives, and pipes + +All `NgModules` have been removed. Components, directives, and pipes have been made standalone and some of the suffixes +(Directive, Component) have been dropped. + +Here is an example of code before and after the migration: + +**Before:** + +```ts +import {DisableControlDirectiveModule, AmountFormatModule, InputFilterModule} from '@angular-ru/cdk/directives'; +import {MutableTypePipeModule, DeepPathPipeModule} from '@angular-ru/cdk/pipes'; + +@Component({ + //... + imports: [ + DisableControlDirectiveModule, + AmountFormatModule, + InputFilterModule, + MutableTypePipeModule, + DeepPathPipeModule, + ], +}) +export class MyComponent {} +``` + +**After:** + +```ts +import {DisableControl, AmountFormat, InputFilter} from '@angular-ru/cdk/directives'; +import {MutableTypePipe, DeepPathPipe} from '@angular-ru/cdk/pipes'; + +@Component({ + //... + imports: [DisableControl, AmountFormat, InputFilter, MutableTypePipe, DeepPathPipe], +}) +export class MyComponent {} +``` + +### `FeatureModule.forRoot()` module providers have been replaced with `provideFeature()` environment providers + +| **Module provider** | **Environment provider** | +| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `InputFilterModule.forRoot()` | `provideInputFilter()` | +| `AmountFormatModule.forRoot()` | `provideAmountFormat()` | +| `DataHttpClientModule.forRoot()` | `provideDataHttpClientOptions()` | +| `DataHttpClientModule.forFeature()` | `provideDataHttpClientClients()` | +| `ExcelBuilderModule.forRoot()` | `provideExcelBuilder()` | +| `EXCEL_BUILDER_NGX_TRANSLATE_FALLBACK_PROVIDER` | `provideExcelBuilderNgxTranslateFallback()` | +| `LoggerModule.forRoot()` | `provideLogger()` | +| `NgxsDataPluginModule.forRoot()` | `provideNgxsDataPlugin()` | +| `NgxsDataPluginModule.forRoot([NGXS_DATA_STORAGE_EXTENSION, NGXS_DATA_STORAGE_CONTAINER]),` | `provideNgxsDataPlugin(withNgxsDataStorage())` | +| `NgxsDataPluginModule.forRoot([MY_FIRST_EXTENSION, MY_SECOND_EXTENSION])` | `provideNgxsDataPlugin(MY_FIRST_EXTENSION, MY_SECOND_EXTENSION)` | +| `TooltipModule.forRoot()` | `provideTooltip()` | +| `TableBuilderModule.forRoot()` | `provideVirtualTable()` | +| `DateSuggestionModule.forRoot()` | `provideDateSuggestion()` | +| `PlainTableComposerModule.forRoot()` | `providePlainTableComposer()` | +| `TableClipboardModule` | `provideTableClipboard()` | +| `WebsocketModule.forRoot()` | `provideWebsocket()` | + +**Before:** + +```ts +export const appConfig: ApplicationConfig = { + providers: [importProvidersFrom(NgxsDataPluginModule.forRoot())], +}; +``` + +**After:** + +```ts +export const appConfig: ApplicationConfig = { + providers: [provideNgxsDataPlugin()], +}; +``` + +### NGXS Data Plugin Extensions + +All ngxs data plugin extensions now have to be environment providers. + +**Before:** + +```ts +import {NgxsDataExtension} from '@angular-ru/ngxs/typings'; + +export const MY_FIRST_EXTENSION: NgxsDataExtension = { + provide: NGXS_PLUGINS, + useClass: MySuperService, + multi: true, +}; + +export const MY_SECOND_EXTENSION: NgxsDataExtension = [ + { + provide: NGXS_PLUGINS, + useClass: FeatureService, + multi: true, + }, + { + provide: MyInjectableToken, + useFactory: (): MyFactory => new MyFactory(), + }, +]; + +NgxsDataPluginModule.forRoot([MY_FIRST_EXTENSION, MY_SECOND_EXTENSION]); +``` + +**After:** + +```ts +import {makeEnvironmentProviders} from '@angular/core'; +import {withNgxsPlugin} from '@ngxs/store'; + +export const MY_FIRST_EXTENSION = withNgxsPlugin(MySuperService); + +export const MY_SECOND_EXTENSION = makeEnvironmentProviders([ + withNgxsPlugin(FeatureService), + { + provide: MyInjectableToken, + useFactory: (): MyFactory => new MyFactory(), + }, +]); + +provideNgxsDataPlugin(MY_FIRST_EXTENSION, MY_SECOND_EXTENSION); +``` + +### Virtual Table changes and deprecations + +- `TableBuilderModule.forRoot()` module provider has been replaced by `provideVirtualTable()` environment provider. +- `TableBuilderModule` has been replaced by `VirtualTable` readonly array of standalone component. + +**Before:** + +```ts +import {TableBuilderModule} from '@angular-ru/cdk/virtual-table'; + +@Component({ + //... + imports: [TableBuilderModule], +}) +export class MyComponent {} +``` + +**After:** + +```ts +import {VirtualTable} from '@angular-ru/cdk/virtual-table'; + +@Component({ + //... + imports: [VirtualTable], +}) +export class MyComponent {} +``` + +- All decorator inputs have been replaced by signal inputs, so, to read an input value in your component, the input has + to be called as a function. + +**Before:** + +```ts +export class MyComponent { + @ViewChild('table') + table!: TableBuilder; + + constructor() { + // "source" was a normal decorated input + console.log('source:', this.table.source); + } +} +``` + +**After:** + +```ts +export class MyComponent { + @ViewChild('table') + table!: TableBuilder; + + constructor() { + // "source" is now a signal input + console.log('source:', this.table.source()); + } +} +``` + +- Some deprecated properties, methods and pipes have been removed: + - `table.selectionEntries` property has been removed. Use `table.selectedKeyList` instead. + - `tableSelectedItems` pipe has been removed. Use `mapToTableEntries` pipe instead. + + **Before:** + + ```html +

Selected items: {{ (table?.selectionEntries | tableSelectedItems).length }}

+ ``` + + **After:** + + ```html +

Selected items: {{ (table?.selectedKeyList | mapToTableEntries).length }}

+ ``` + + - `table.selectedItems` property has been removed. Use `table.getSelectedItems()` method instead. diff --git a/docs/ngxs/data-action.md b/docs/ngxs/data-action.md index 9c8d359f7..28105eea8 100644 --- a/docs/ngxs/data-action.md +++ b/docs/ngxs/data-action.md @@ -514,7 +514,7 @@ export class PersonState extends NgxsImmutableDataRepository { super(); } - // Note: Also can be configured globally by providing custom NGXS_DATA_CONFIG + // Note: Also can be configured globally using provideNgxsDataPlugin({subscribeRequired: false}) @DataAction({subscribeRequired: false}) public getContent(): Observable { return this.personService.fetchAll().pipe(tap((content: PersonModel): void => this.setState(content))); @@ -525,23 +525,19 @@ export class PersonState extends NgxsImmutableDataRepository { The same behavior can be achieved globally for all `@DataAction` in the app by providing a global config property. ```typescript -@NgModule({ - declarations: [AppComponent], - imports: [ - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - FormsModule, - ReactiveFormsModule, - NgxsModule.forRoot([], { - developmentMode: !environment.production, - executionStrategy: NoopNgxsExecutionStrategy, - }), - NgxsLoggerPluginModule.forRoot(), - NgxsDataPluginModule.forRoot([NGXS_DATA_STORAGE_EXTENSION, NGXS_DATA_STORAGE_CONTAINER]), +export const appConfig: ApplicationConfig = { + providers: [ + provideRouter(routes), + provideAnimationsAsync(), + provideStore( + [], + { + developmentMode: !environment.production, + }, + withNgxsLoggerPlugin(), + withNgxsNoopExecutionStrategy(), + ), + provideNgxsDataPlugin({dataActionSubscribeRequired: false}, withNgxsDataStorage()), ], - providers: [{provide: NGXS_DATA_CONFIG, useValue: {dataActionSubscribeRequired: false}}], - bootstrap: [AppComponent], -}) -export class AppModule {} +}; ``` diff --git a/docs/ngxs/extension-api.md b/docs/ngxs/extension-api.md index 5a8923616..6824c8269 100644 --- a/docs/ngxs/extension-api.md +++ b/docs/ngxs/extension-api.md @@ -1,53 +1,46 @@ ## Extension API ```typescript -import {NgxsDataPluginModule} from '@angular-ru/ngxs'; -// .. +import {provideNgxsDataPlugin} from '@angular-ru/ngxs'; +// ... -@NgModule({ - imports: [ - NgxsDataPluginModule.forRoot([ +export const appConfig: ApplicationConfig = { + providers: [ + provideNgxsDataPlugin( MY_FIRST_EXTENSION, MY_SECOND_EXTENSION, MY_THIRD_EXTENSION, MY_FOURTH_EXTENSION, MY_FIFTH_EXTENSION, - ]), + ), ], -}) -export class AppModule {} +}; ``` -`my-extensions.ts` - you can define any providers in your module: +`my-extensions.ts` - you can define any providers in your app config: ```typescript -import {NgxsDataExtension} from '@angular-ru/ngxs/typings'; -// .. +import {makeEnvironmentProviders} from '@angular/core'; +import {withNgxsPlugin} from '@ngxs/store'; -export const MY_FIRST_EXTENSION: NgxsDataExtension = { - provide: NGXS_PLUGINS, - useClass: MySuperService, - multi: true, -}; +export const MY_FIRST_EXTENSION = withNgxsPlugin(MySuperService); -export const MY_SECOND_EXTENSION: NgxsDataExtension = [ - { - provide: NGXS_PLUGINS, - useClass: FeatureService, - multi: true, - }, +export const MY_SECOND_EXTENSION = makeEnvironmentProviders([ + withNgxsPlugin(FeatureService), { provide: MyInjectableToken, useFactory: (): MyFactory => new MyFactory(), }, -]; +]); -export const MY_THIRD_EXTENSION: NgxsDataExtension = [MySuperPluginA.forRoot(), MySuperPluginB.forRoot()]; +export const MY_THIRD_EXTENSION = makeEnvironmentProviders([provideMySuperPluginA(), provideMySuperPluginB()]); -export const MY_FOURTH_EXTENSION: NgxsDataExtension = MyInjectableService; +export const MY_FOURTH_EXTENSION = makeEnvironmentProviders([MyInjectableService]); -export const MY_FIFTH_EXTENSION: NgxsDataExtension = { - provide: MY_TOKEN, - useValue: 'VALUE', -}; +export const MY_FIFTH_EXTENSION = makeEnvironmentProviders([ + { + provide: MY_TOKEN, + useValue: 'VALUE', + }, +]); ``` diff --git a/docs/ngxs/immutability.md b/docs/ngxs/immutability.md index 32a77debe..6d40a6dee 100644 --- a/docs/ngxs/immutability.md +++ b/docs/ngxs/immutability.md @@ -46,7 +46,7 @@ console.log(me); // { name: 'Rob', age: 29 } If you want to freeze an entire object hierarchy then this requires a traversal of the object tree and for the Object.freeze operation to be applied to all objects. This is commonly known as deep freezing. When the developmentMode -option is enabled in the NGXS forRoot module configuration a +option is enabled in the NGXS provider configuration a [deep freeze](https://github.com/ngxs/store/blob/1a85af3ec36b669bb7491332cf62fc6db202e955/packages/store/src/internal/state-operations.ts#L46) operation is applied to any new objects in the state tree to help prevent side effects. Since this imposes some performance costs it is only enabled in development mode. @@ -62,16 +62,14 @@ denote properties as readonly across the entire depth of an object. ```typescript @StateRepository() @State({ - name: 'todo', - defaults: [] + name: 'todo', + defaults: [], }) @Injectable() export class TodoState extends NgxsImmutableDataRepository { - reversed$ = this.state$.pipe( - map( state => state.reverse() ) - ); ^ - | - |_____ TS Compile error: property 'reverse' does not exist on type + reversed$ = this.state$.pipe(map((state) => state.reverse())); // ^ + // | + // |_____ TS Compile error: property 'reverse' does not exist on type } ``` @@ -79,19 +77,21 @@ Thus, the developer will not be able to make his own mistake. If he mutates the methods. If you need to use states for set input property: ```typescript -import { Immutable } from '@angular-ru/cdk/typings'; +import {Immutable} from '@angular-ru/cdk/typings'; -@Component({ .. }) +@Component({ + //.. +}) class TodoComponent { - @Input() public data: Immutable; + @Input() public data: Immutable; } @Component({ - selector: 'app', - template: '' + selector: 'app', + template: '', }) class AppComponent { - constructor(public todos: TodosState) {} + constructor(public todos: TodosState) {} } ``` @@ -104,42 +104,34 @@ However, if you really need to cast to mutable, you can do this in several ways: ```typescript @StateRepository() @State({ - name: 'todo', - defaults: [] + name: 'todo', + defaults: [], }) @Injectable() export class TodoState extends NgxsImmutableDataRepository { - mutableState$ = this.state$.pipe( - map( state => state as string[] ) - ); + mutableState$ = this.state$.pipe(map((state) => state as string[])); +} ``` or `Into template` without creating `mutableState$`: ```typescript -import {MutableTypeModule} from '@angular-ru/cdk/pipes'; +import {MutableTypePipe} from '@angular-ru/cdk/pipes'; -@NgModule({ - imports: [ - // .. - MutableTypeModule, - ], +@Component({ + // ... }) -export class AppModuleOrMyLazyModule {} -``` - -```typescript -@Component({ .. }) class TodoComponent { - @Input() public data: string[]; + @Input() public data: string[]; } @Component({ - selector: 'app', - template: '' + selector: 'app', + imports: [MutableTypePipe], + template: '', }) class AppComponent { - constructor(public todos: TodosState) {} + constructor(public todos: TodosState) {} } ``` diff --git a/docs/ngxs/persistence-state.md b/docs/ngxs/persistence-state.md index a06713bb0..a99a46473 100644 --- a/docs/ngxs/persistence-state.md +++ b/docs/ngxs/persistence-state.md @@ -1,14 +1,13 @@ ## @Persistence ```typescript -import {NgxsDataPluginModule} from '@angular-ru/ngxs'; -import {NGXS_DATA_STORAGE_PLUGIN} from '@angular-ru/ngxs/storage'; +import {provideStore} from '@ngxs/store'; +import {provideNgxsDataPlugin} from '@angular-ru/ngxs'; +import {withNgxsDataStorage} from '@angular-ru/ngxs/storage'; -@NgModule({ - // .. - imports: [NgxsModule.forRoot([TodoState]), NgxsDataPluginModule.forRoot([NGXS_DATA_STORAGE_PLUGIN])], -}) -export class AppModule {} +export const appConfig: ApplicationConfig = { + providers: [provideStore([TodoState]), provideNgxsDataPlugin(withNgxsDataStorage())], +}; ``` ```typescript @@ -341,26 +340,31 @@ export class TodoState extends NgxsDataRepository {} By default, key search uses the prefix `@ngxs.store.`, but you can override the prefix: ```typescript -import {NGXS_DATA_STORAGE_PREFIX_TOKEN, NGXS_DATA_STORAGE_PLUGIN} from '@angular-ru/ngxs/storage'; - -@NgModule({ - imports: [NgxsModule.forRoot([AppState]), NgxsDataPluginModule.forRoot(NGXS_DATA_STORAGE_PLUGIN)], - providers: [{provide: NGXS_DATA_STORAGE_PREFIX_TOKEN, useValue: '@myCompany.store.'}], -}) -export class AppModule {} +import {provideNgxsDataPlugin} from '@angular-ru/ngxs'; +import {NGXS_DATA_STORAGE_PREFIX_TOKEN, withNgxsDataStorage} from '@angular-ru/ngxs/storage'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideStore([AppState]), + provideNgxsDataPlugin(withNgxsDataStorage()), + {provide: NGXS_DATA_STORAGE_PREFIX_TOKEN, useValue: '@myCompany.store.'}, + ], +}; ``` ### Use base64 for decode/encode data in storage by default everything ```typescript -import {STORAGE_DECODE_TYPE} from '@angular-ru/ngxs/typings'; -import {NGXS_DATA_STORAGE_DECODE_TYPE_TOKEN, NGXS_DATA_STORAGE_PLUGIN} from '@angular-ru/ngxs/storage'; - -@NgModule({ - imports: [NgxsModule.forRoot([AppState]), NgxsDataPluginModule.forRoot(NGXS_DATA_STORAGE_PLUGIN)], - providers: [{provide: NGXS_DATA_STORAGE_DECODE_TYPE_TOKEN, useValue: STORAGE_DECODE_TYPE.BASE64}], -}) -export class AppModule {} +import {provideNgxsDataPlugin} from '@angular-ru/ngxs'; +import {NGXS_DATA_STORAGE_DECODE_TYPE_TOKEN, STORAGE_DECODE_TYPE, withNgxsDataStorage} from '@angular-ru/ngxs/storage'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideStore([AppState]), + provideNgxsDataPlugin(withNgxsDataStorage()), + {provide: NGXS_DATA_STORAGE_DECODE_TYPE_TOKEN, useValue: STORAGE_DECODE_TYPE.BASE64}, + ], +}; ``` ### Nested states diff --git a/docs/ngxs/quick-start.md b/docs/ngxs/quick-start.md index 44e596d2f..08b2fb4e5 100644 --- a/docs/ngxs/quick-start.md +++ b/docs/ngxs/quick-start.md @@ -1,20 +1,18 @@ ## Quick Start -`app.module.ts` +`app.config.ts` ```typescript -import {NgxsModule} from '@ngxs/store'; -import {NgxsDataPluginModule} from '@angular-ru/ngxs'; +import {provideStore} from '@ngxs/store'; +import {provideNgxsDataPlugin} from '@angular-ru/ngxs'; -@NgModule({ - imports: [ +export const appConfig: ApplicationConfig = { + providers: [ // .. - NgxsModule.forRoot([AppState]), - NgxsDataPluginModule.forRoot(), + provideStore([AppState]), + provideNgxsDataPlugin(), ], - // .. -}) -export class AppModule {} +}; ``` `count.state.ts` @@ -62,14 +60,14 @@ export class CountState extends NgxsDataRepository { `app.component.ts` ```typescript -... - @Component({ selector: 'app', template: ` Selection: - counter.state$ = {{ counter.state$ | async | json }}
- counter.values$ = {{ counter.values$ | async }}
+ counter.state$ = {{ counter.state$ | async | json }} +
+ counter.values$ = {{ counter.values$ | async }} +
Actions: @@ -78,12 +76,12 @@ export class CountState extends NgxsDataRepository { ngModel: (delay: 300ms) - ` + `, }) export class AppComponent { constructor(public counter: CountState) {} @@ -95,16 +93,15 @@ export class AppComponent { `Need provide logger-plugin` ```typescript -import {NgxsLoggerPluginModule} from '@ngxs/logger-plugin'; +import {withNgxsLoggerPlugin} from '@ngxs/logger-plugin'; +import {provideStore, withNgxsNoopExecutionStrategy} from '@ngxs/store'; -@NgModule({ - imports: [ - // .. - NgxsLoggerPluginModule.forRoot(), +export const appConfig: ApplicationConfig = { + providers: [ + provideStore([AppState], withNgxsLoggerPlugin(), withNgxsNoopExecutionStrategy()), + // ... ], - // .. -}) -export class AppModule {} +}; ``` ```typescript diff --git a/jest.config.ts b/jest.config.ts index 8aa8b6448..73461ba5d 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -21,8 +21,7 @@ const config: JestConfigWithTsJest = { 'jest-preset-angular', { tsconfig: resolve(__dirname, 'tsconfig.spec.json'), - stringifyContentPathRegex: '\\.html$', - isolatedModules: true, + stringifyContentPathRegex: String.raw`\.html$`, diagnostics: true, }, ], diff --git a/libs/cdk/CHANGELOG.md b/libs/cdk/CHANGELOG.md index c78a4fc09..933bd9622 100644 --- a/libs/cdk/CHANGELOG.md +++ b/libs/cdk/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [19.0.0](https://github.com/Angular-RU/angular-ru-sdk/compare/v18.11.0...v19.0.0) (2025-09-01) + +### Bug Fixes + +* **cdk:** virtual table now correctly rerenders after setting sort types manually multiple times ([c5f715c](https://github.com/Angular-RU/angular-ru-sdk/commit/c5f715c3098aa53f18d66da72b49bacdd190524a)) + +### Features + +* angular v20 and NX v21 ([1aa382d](https://github.com/Angular-RU/angular-ru-sdk/commit/1aa382d505962c260c6f8a871bdb7d58d2b8b1cb)) +* standalone components and pipes; root providers instead of forRoot(); removal of ngModules ([729959c](https://github.com/Angular-RU/angular-ru-sdk/commit/729959c201623ffc4406d680d46908059d9a5754)) + # [15.2.0](https://github.com/Angular-RU/angular-ru-sdk/compare/@angular-ru/cdk@15.1.0...@angular-ru/cdk@15.2.0) (2024-04-10) ### Features diff --git a/libs/cdk/array/by-property-value.ts b/libs/cdk/array/by-property-value.ts index 956b1cbe0..a648ad23b 100644 --- a/libs/cdk/array/by-property-value.ts +++ b/libs/cdk/array/by-property-value.ts @@ -7,9 +7,9 @@ export function byPropertyValue< value: ValueType, ): ( arrayItem: ArrayEntryType, -) => arrayItem is ArrayEntryType & {[key in ArrayEntryTypeKey]: ValueType} { +) => arrayItem is ArrayEntryType & Record { return ( arrayItem: ArrayEntryType, - ): arrayItem is ArrayEntryType & {[key in ArrayEntryTypeKey]: ValueType} => + ): arrayItem is ArrayEntryType & Record => arrayItem[key] === value; } diff --git a/libs/cdk/array/update-array.ts b/libs/cdk/array/update-array.ts index 98351120d..e21a260f2 100644 --- a/libs/cdk/array/update-array.ts +++ b/libs/cdk/array/update-array.ts @@ -23,8 +23,8 @@ export function updateArray( const skipIndexes = new Set(); // eslint-disable-next-line unicorn/no-for-loop - for (let i = 0; i < preparedSourceArray.length; i++) { - const currentItem: T = preparedSourceArray[i]; + for (const element of preparedSourceArray) { + const currentItem: T = element; let updated = false; // eslint-disable-next-line unicorn/no-for-loop diff --git a/libs/cdk/date/date-suggestion/date-suggestion.composer.ts b/libs/cdk/date/date-suggestion/date-suggestion.composer.ts index 32449294e..a54ad03c9 100644 --- a/libs/cdk/date/date-suggestion/date-suggestion.composer.ts +++ b/libs/cdk/date/date-suggestion/date-suggestion.composer.ts @@ -1,4 +1,4 @@ -import {Inject, Injectable, Injector} from '@angular/core'; +import {inject, Injectable, Injector} from '@angular/core'; import {Nullable} from '@angular-ru/cdk/typings'; import {isNil} from '@angular-ru/cdk/utils'; @@ -10,11 +10,11 @@ import {SUGGESTION_STRATEGY_MAP} from './tokens/suggestion-strategy-map'; @Injectable() export class DateSuggestionComposer { - constructor( - @Inject(SUGGESTION_STRATEGY_MAP) - private readonly suggestionStrategyMap: SuggestionStrategyMap, - private readonly injector: Injector, - ) {} + private readonly suggestionStrategyMap = inject>( + SUGGESTION_STRATEGY_MAP, + ); + + private readonly injector = inject(Injector); public getSuggestions(): StrategyKeys[] { return Object.keys(this.suggestionStrategyMap) as StrategyKeys[]; diff --git a/libs/cdk/date/date-suggestion/date-suggestion.module.ts b/libs/cdk/date/date-suggestion/date-suggestion.module.ts deleted file mode 100644 index b4813212a..000000000 --- a/libs/cdk/date/date-suggestion/date-suggestion.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {ModuleWithProviders, NgModule} from '@angular/core'; - -import {DateSuggestionComposer} from './date-suggestion.composer'; -import {SuggestionStrategyMap} from './domain/types/suggestion-strategy-map'; -import {DEFAULT_SUGGESTION_STRATEGY_MAP} from './properties/default-suggestion-strategy-map'; -import {SUGGESTION_STRATEGY_MAP} from './tokens/suggestion-strategy-map'; - -@NgModule() -export class DateSuggestionModule { - public static forRoot( - customSuggestionStrategyMap?: SuggestionStrategyMap, - ): ModuleWithProviders { - return { - ngModule: DateSuggestionModule, - providers: [ - DateSuggestionComposer, - { - provide: SUGGESTION_STRATEGY_MAP, - useValue: - customSuggestionStrategyMap ?? DEFAULT_SUGGESTION_STRATEGY_MAP, - }, - ], - }; - } -} diff --git a/libs/cdk/date/date-suggestion/date-suggestion.provider.ts b/libs/cdk/date/date-suggestion/date-suggestion.provider.ts new file mode 100644 index 000000000..d65342dfa --- /dev/null +++ b/libs/cdk/date/date-suggestion/date-suggestion.provider.ts @@ -0,0 +1,18 @@ +import {makeEnvironmentProviders} from '@angular/core'; + +import {DateSuggestionComposer} from './date-suggestion.composer'; +import {SuggestionStrategyMap} from './domain/types/suggestion-strategy-map'; +import {DEFAULT_SUGGESTION_STRATEGY_MAP} from './properties/default-suggestion-strategy-map'; +import {SUGGESTION_STRATEGY_MAP} from './tokens/suggestion-strategy-map'; + +export function provideDateSuggestion( + customSuggestionStrategyMap?: SuggestionStrategyMap, +) { + return makeEnvironmentProviders([ + DateSuggestionComposer, + { + provide: SUGGESTION_STRATEGY_MAP, + useValue: customSuggestionStrategyMap ?? DEFAULT_SUGGESTION_STRATEGY_MAP, + }, + ]); +} diff --git a/libs/cdk/date/date-suggestion/domain/types/suggestion-strategy-map.ts b/libs/cdk/date/date-suggestion/domain/types/suggestion-strategy-map.ts index 585997f49..1366d2bdf 100644 --- a/libs/cdk/date/date-suggestion/domain/types/suggestion-strategy-map.ts +++ b/libs/cdk/date/date-suggestion/domain/types/suggestion-strategy-map.ts @@ -1,6 +1,7 @@ import {SuggestionStrategyDescriptor} from '../interfaces/suggestion-strategy-descriptor'; import {StrategyKey} from './strategy-key'; -export type SuggestionStrategyMap = { - [P in Keys]: SuggestionStrategyDescriptor; -}; +export type SuggestionStrategyMap = Record< + Keys, + SuggestionStrategyDescriptor +>; diff --git a/libs/cdk/date/date-suggestion/index.ts b/libs/cdk/date/date-suggestion/index.ts new file mode 100644 index 000000000..914957c91 --- /dev/null +++ b/libs/cdk/date/date-suggestion/index.ts @@ -0,0 +1,15 @@ +export {DateSuggestionComposer} from './date-suggestion.composer'; +export {provideDateSuggestion} from './date-suggestion.provider'; +export {DefaultDateIntervalSuggestion} from './domain/enums/default-date-interval-suggestion'; +export {DateSuggestionStrategy} from './domain/interfaces/date-suggestion-strategy'; +export {SuggestionStrategyDescriptor} from './domain/interfaces/suggestion-strategy-descriptor'; +export {SuggestionStrategyMap} from './domain/types/suggestion-strategy-map'; +export {DEFAULT_SUGGESTION_STRATEGY_MAP} from './properties/default-suggestion-strategy-map'; +export {DateSuggestionCalendarWeekStrategy} from './strategies/date-suggestion-calendar-week.strategy'; +export {DateSuggestionFirstDayOfIntervalStrategy} from './strategies/date-suggestion-first-day-of-interval.strategy'; +export {DateSuggestionLastDaysOfIntervalStrategy} from './strategies/date-suggestion-last-days-of-interval.strategy'; +export {DateSuggestionLastFewDaysStrategy} from './strategies/date-suggestion-last-few-days.strategy'; +export {DateSuggestionSomeDayAgoStrategy} from './strategies/date-suggestion-some-day-ago.strategy'; +export {DAYS_COUNT} from './tokens/days-count'; +export {DayOfWeek, FIRST_DAY_OF_WEEK} from './tokens/first-day-of-week'; +export {SUGGESTION_STRATEGY_MAP} from './tokens/suggestion-strategy-map'; diff --git a/libs/cdk/date/date-suggestion/strategies/date-suggestion-calendar-week.strategy.ts b/libs/cdk/date/date-suggestion/strategies/date-suggestion-calendar-week.strategy.ts index a47c5ac75..9caa673a3 100644 --- a/libs/cdk/date/date-suggestion/strategies/date-suggestion-calendar-week.strategy.ts +++ b/libs/cdk/date/date-suggestion/strategies/date-suggestion-calendar-week.strategy.ts @@ -1,4 +1,4 @@ -import {Inject, Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {AbstractControl} from '@angular/forms'; import {DateIntervalDescriptor} from '@angular-ru/cdk/typings'; @@ -12,7 +12,7 @@ const WEEK_LENGTH = 7; @Injectable() export class DateSuggestionCalendarWeekStrategy implements DateSuggestionStrategy { - constructor(@Inject(FIRST_DAY_OF_WEEK) private readonly firstDayOfWeek: DayOfWeek) {} + private readonly firstDayOfWeek = inject(FIRST_DAY_OF_WEEK); public updateIntervalFor( control: AbstractControl, diff --git a/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-days-of-interval.strategy.ts b/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-days-of-interval.strategy.ts index 7af16858d..ed5851018 100644 --- a/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-days-of-interval.strategy.ts +++ b/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-days-of-interval.strategy.ts @@ -1,4 +1,4 @@ -import {Inject, Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {AbstractControl} from '@angular/forms'; import {DateIntervalDescriptor} from '@angular-ru/cdk/typings'; @@ -10,7 +10,7 @@ import {DAYS_COUNT} from '../tokens/days-count'; @Injectable() export class DateSuggestionLastDaysOfIntervalStrategy implements DateSuggestionStrategy { - constructor(@Inject(DAYS_COUNT) private readonly lastDaysCount: number) {} + private readonly lastDaysCount = inject(DAYS_COUNT); public updateIntervalFor( control: AbstractControl, diff --git a/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-few-days.strategy.ts b/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-few-days.strategy.ts index 93710f8bc..7779d6a83 100644 --- a/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-few-days.strategy.ts +++ b/libs/cdk/date/date-suggestion/strategies/date-suggestion-last-few-days.strategy.ts @@ -1,4 +1,4 @@ -import {Inject, Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {AbstractControl} from '@angular/forms'; import {DateIntervalDescriptor} from '@angular-ru/cdk/typings'; @@ -10,7 +10,7 @@ import {DAYS_COUNT} from '../tokens/days-count'; @Injectable() export class DateSuggestionLastFewDaysStrategy implements DateSuggestionStrategy { - constructor(@Inject(DAYS_COUNT) private readonly daysCount: number) {} + private readonly daysCount = inject(DAYS_COUNT); public updateIntervalFor( control: AbstractControl, diff --git a/libs/cdk/date/date-suggestion/strategies/date-suggestion-some-day-ago.strategy.ts b/libs/cdk/date/date-suggestion/strategies/date-suggestion-some-day-ago.strategy.ts index ba10f079d..1cbbfa660 100644 --- a/libs/cdk/date/date-suggestion/strategies/date-suggestion-some-day-ago.strategy.ts +++ b/libs/cdk/date/date-suggestion/strategies/date-suggestion-some-day-ago.strategy.ts @@ -1,4 +1,4 @@ -import {Inject, Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {AbstractControl} from '@angular/forms'; import {DateIntervalDescriptor, Timestamp} from '@angular-ru/cdk/typings'; @@ -10,7 +10,7 @@ import {DAYS_COUNT} from '../tokens/days-count'; @Injectable() export class DateSuggestionSomeDayAgoStrategy implements DateSuggestionStrategy { - constructor(@Inject(DAYS_COUNT) private readonly daysCount: number) {} + private readonly daysCount = inject(DAYS_COUNT); public updateIntervalFor( control: AbstractControl, diff --git a/libs/cdk/date/index.ts b/libs/cdk/date/index.ts index ee7512201..e05700595 100644 --- a/libs/cdk/date/index.ts +++ b/libs/cdk/date/index.ts @@ -1,20 +1,6 @@ export {DateRange} from './date-range'; export {dateStringToDate} from './date-string-to-date'; -export {DateSuggestionComposer} from './date-suggestion/date-suggestion.composer'; -export {DateSuggestionModule} from './date-suggestion/date-suggestion.module'; -export {DefaultDateIntervalSuggestion} from './date-suggestion/domain/enums/default-date-interval-suggestion'; -export {DateSuggestionStrategy} from './date-suggestion/domain/interfaces/date-suggestion-strategy'; -export {SuggestionStrategyDescriptor} from './date-suggestion/domain/interfaces/suggestion-strategy-descriptor'; -export {SuggestionStrategyMap} from './date-suggestion/domain/types/suggestion-strategy-map'; -export {DEFAULT_SUGGESTION_STRATEGY_MAP} from './date-suggestion/properties/default-suggestion-strategy-map'; -export {DateSuggestionCalendarWeekStrategy} from './date-suggestion/strategies/date-suggestion-calendar-week.strategy'; -export {DateSuggestionFirstDayOfIntervalStrategy} from './date-suggestion/strategies/date-suggestion-first-day-of-interval.strategy'; -export {DateSuggestionLastDaysOfIntervalStrategy} from './date-suggestion/strategies/date-suggestion-last-days-of-interval.strategy'; -export {DateSuggestionLastFewDaysStrategy} from './date-suggestion/strategies/date-suggestion-last-few-days.strategy'; -export {DateSuggestionSomeDayAgoStrategy} from './date-suggestion/strategies/date-suggestion-some-day-ago.strategy'; -export {DAYS_COUNT} from './date-suggestion/tokens/days-count'; -export {DayOfWeek, FIRST_DAY_OF_WEEK} from './date-suggestion/tokens/first-day-of-week'; -export {SUGGESTION_STRATEGY_MAP} from './date-suggestion/tokens/suggestion-strategy-map'; +export * from './date-suggestion'; export {endOfDay} from './end-of-day'; export {getToday} from './get-today'; export {isDateValid} from './is-date-valid'; diff --git a/libs/cdk/decorators/attribute-boolean.ts b/libs/cdk/decorators/attribute-boolean.ts index afbe83e72..8808aa068 100644 --- a/libs/cdk/decorators/attribute-boolean.ts +++ b/libs/cdk/decorators/attribute-boolean.ts @@ -6,7 +6,7 @@ export function AttributeBoolean(): PropertyDecorator { return function ( prototype: PlainObject, key: string | symbol, - ): PropertyDescriptor & ThisType { + ): PropertyDescriptor & Record { const descriptor: Nullable = Object.getOwnPropertyDescriptor( prototype, key, @@ -23,7 +23,7 @@ export function AttributeBoolean(): PropertyDecorator { // eslint-disable-next-line @typescript-eslint/unbound-method return isNotNil(descriptor?.get) ? descriptor?.get.call(this) - : this[uniqueRefKey] ?? false; + : (this[uniqueRefKey] ?? false); }, }; }; diff --git a/libs/cdk/directives/amount-format/amount-format.directive.ts b/libs/cdk/directives/amount-format/amount-format.directive.ts index 3d8712222..e2a5efb13 100644 --- a/libs/cdk/directives/amount-format/amount-format.directive.ts +++ b/libs/cdk/directives/amount-format/amount-format.directive.ts @@ -2,13 +2,13 @@ import { AfterViewInit, ChangeDetectorRef, Directive, + effect, ElementRef, - Inject, - Input, + inject, + model, NgZone, OnDestroy, OnInit, - Optional, } from '@angular/core'; import {NgControl} from '@angular/forms'; import {gaussRound, getFractionSeparator, toNumber} from '@angular-ru/cdk/number'; @@ -26,7 +26,12 @@ import {AMOUNT_FORMAT_OPTIONS, DEFAULT_AMOUNT_OPTIONS} from './amount-format.pro import {AmountOptions} from './amount-options'; @Directive({selector: '[amountFormat]'}) -export class AmountFormatDirective implements OnInit, AfterViewInit, OnDestroy { +export class AmountFormat implements OnInit, AfterViewInit, OnDestroy { + private readonly elementRef = inject>(ElementRef); + private readonly ngControl = inject(NgControl, {optional: true}); + private readonly ngZone = inject(NgZone, {optional: true}); + private readonly cd = inject(ChangeDetectorRef, {optional: true}); + private readonly subscriptions: Subscription = new Subscription(); private previousLang: Nullable = null; private readonly maximumFractionDigits: number = 3; @@ -35,16 +40,23 @@ export class AmountFormatDirective implements OnInit, AfterViewInit, OnDestroy { private markedAsDirty = true; private cursorPointer = 0; - constructor( - @Inject(ElementRef) private readonly elementRef: ElementRef, - @Inject(AMOUNT_FORMAT_OPTIONS) globalOptions: AmountOptions, - @Inject(NgControl) @Optional() private readonly ngControl?: NgControl, - @Inject(NgZone) @Optional() private readonly ngZone?: NgZone, - @Inject(ChangeDetectorRef) @Optional() private readonly cd?: ChangeDetectorRef, - ) { + constructor() { + const globalOptions = inject(AMOUNT_FORMAT_OPTIONS); + + this.listenToOptionInputChanges(); this.setFirstLocalOptionsByGlobal(globalOptions); } + private listenToOptionInputChanges() { + effect(() => { + const options: Partial = this.amountFormatOptions(); + + this.options = {...this.options, ...(options ?? {})}; + + this.recalculateOnOptionChange(); + }); + } + public get isInAngularZone(): boolean { return this.isInsideAngularZone; } @@ -57,19 +69,11 @@ export class AmountFormatDirective implements OnInit, AfterViewInit, OnDestroy { return this.elementRef.nativeElement; } - public get amountFormatOptions(): Partial { - return this.options; - } - - @Input() - public set amountFormatOptions(options: Partial) { - this.options = {...this.options, ...(options ?? {})}; - this.recalculateWhenChangesOptions(); - } + public readonly amountFormatOptions = model>(this.options); public setLang(lang: string): void { this.options.lang = lang; - this.recalculateWhenChangesOptions(); + this.recalculateOnOptionChange(); } public getCursorPosition(): number { @@ -187,9 +191,9 @@ export class AmountFormatDirective implements OnInit, AfterViewInit, OnDestroy { let lastSymbolsAsZeroDot: string = maximumFractionDigits === 0 ? '' - : this.element.value - .match(new RegExp(`(\\${fraction})(.+)?`))?.[0] - ?.replace(/,|./, '') ?? ''; + : (new RegExp(`(\\${fraction})(.+)?`) + .exec(this.element.value)?.[0] + ?.replace(/,|./, '') ?? ''); const isOverflowGaussRound: boolean = lastSymbolsAsZeroDot.length > this.maximumFractionDigits; @@ -322,9 +326,10 @@ export class AmountFormatDirective implements OnInit, AfterViewInit, OnDestroy { private setFirstLocalOptionsByGlobal(options: AmountOptions): void { this.options = deepClone(options); this.previousLang = this.options.lang; + this.amountFormatOptions.set(this.options); } - private recalculateWhenChangesOptions(): void { + private recalculateOnOptionChange(): void { const value: number = toNumber( this.element.value, this.previousLang ?? this.options.lang, @@ -332,7 +337,7 @@ export class AmountFormatDirective implements OnInit, AfterViewInit, OnDestroy { this.element.value = value.toLocaleString( this.options.lang, - this.options.formatOptions, + this.options.formatOptions ?? {}, ); this.previousLang = this.options.lang; this.format(); diff --git a/libs/cdk/directives/amount-format/amount-format.module.ts b/libs/cdk/directives/amount-format/amount-format.module.ts deleted file mode 100644 index 9f676238d..000000000 --- a/libs/cdk/directives/amount-format/amount-format.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {ModuleWithProviders, NgModule} from '@angular/core'; - -import {AmountFormatDirective} from './amount-format.directive'; -import {AMOUNT_FORMAT_OPTIONS, DEFAULT_AMOUNT_OPTIONS} from './amount-format.properties'; -import {AmountOptions} from './amount-options'; - -@NgModule({ - imports: [CommonModule], - declarations: [AmountFormatDirective], - exports: [AmountFormatDirective], -}) -export class AmountFormatModule { - public static forRoot( - options: Partial = {}, - ): ModuleWithProviders { - return { - ngModule: AmountFormatModule, - providers: [ - { - provide: AMOUNT_FORMAT_OPTIONS, - useValue: {...DEFAULT_AMOUNT_OPTIONS, ...options}, - }, - ], - }; - } -} diff --git a/libs/cdk/directives/amount-format/amount-format.provider.ts b/libs/cdk/directives/amount-format/amount-format.provider.ts new file mode 100644 index 000000000..6190c78ec --- /dev/null +++ b/libs/cdk/directives/amount-format/amount-format.provider.ts @@ -0,0 +1,13 @@ +import {makeEnvironmentProviders} from '@angular/core'; + +import {AMOUNT_FORMAT_OPTIONS, DEFAULT_AMOUNT_OPTIONS} from './amount-format.properties'; +import {AmountOptions} from './amount-options'; + +export function provideAmountFormat(options: Partial = {}) { + return makeEnvironmentProviders([ + { + provide: AMOUNT_FORMAT_OPTIONS, + useValue: {...DEFAULT_AMOUNT_OPTIONS, ...options}, + }, + ]); +} diff --git a/libs/cdk/directives/amount-format/index.ts b/libs/cdk/directives/amount-format/index.ts new file mode 100644 index 000000000..9bc911565 --- /dev/null +++ b/libs/cdk/directives/amount-format/index.ts @@ -0,0 +1,4 @@ +export * from './amount-format.directive'; +export * from './amount-format.properties'; +export * from './amount-format.provider'; +export * from './amount-options'; diff --git a/libs/cdk/directives/convert-case/convert-case.directive.ts b/libs/cdk/directives/convert-case/convert-case.directive.ts index 79ac1b85c..06df1fc83 100644 --- a/libs/cdk/directives/convert-case/convert-case.directive.ts +++ b/libs/cdk/directives/convert-case/convert-case.directive.ts @@ -3,28 +3,19 @@ import { Directive, ElementRef, HostListener, - Inject, - Input, - Optional, + inject, + input, } from '@angular/core'; import {NgControl} from '@angular/forms'; import {toStringValue} from '@angular-ru/cdk/string'; @Directive({selector: '[convertCase]'}) -export class ConvertCaseDirective implements AfterViewInit { - @Input() - public toUpperCase = true; +export class ConvertCase implements AfterViewInit { + private readonly elementRef = inject(ElementRef); + private readonly ngControl = inject(NgControl, {optional: true}); - @Input() - public toLowerCase = false; - - constructor( - @Inject(ElementRef) - private readonly elementRef: ElementRef, - @Inject(NgControl) - @Optional() - private readonly ngControl?: NgControl, - ) {} + public readonly toUpperCase = input(true); + public readonly toLowerCase = input(false); private get element(): HTMLInputElement { return this.elementRef.nativeElement; @@ -34,7 +25,7 @@ export class ConvertCaseDirective implements AfterViewInit { public onInput(): void { const dirtyValue: string = toStringValue(this.element.value); - this.element.value = this.toLowerCase + this.element.value = this.toLowerCase() ? dirtyValue.toLowerCase() : dirtyValue.toUpperCase(); this.ngControl?.reset(this.element.value); diff --git a/libs/cdk/directives/convert-case/convert-case.module.ts b/libs/cdk/directives/convert-case/convert-case.module.ts deleted file mode 100644 index f1f069304..000000000 --- a/libs/cdk/directives/convert-case/convert-case.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; - -import {ConvertCaseDirective} from './convert-case.directive'; - -@NgModule({ - imports: [CommonModule], - declarations: [ConvertCaseDirective], - exports: [ConvertCaseDirective], -}) -export class ConvertCaseModule {} diff --git a/libs/cdk/directives/disable-control/disable-control.directive.ts b/libs/cdk/directives/disable-control/disable-control.directive.ts index 994d26bcb..9af63b0ed 100644 --- a/libs/cdk/directives/disable-control/disable-control.directive.ts +++ b/libs/cdk/directives/disable-control/disable-control.directive.ts @@ -1,19 +1,23 @@ -import {Directive, Inject, Input, Optional} from '@angular/core'; +import {Directive, effect, inject, input} from '@angular/core'; import {NgControl} from '@angular/forms'; import {PlainObject} from '@angular-ru/cdk/typings'; @Directive({selector: '[disableControl]'}) -export class DisableControlDirective { - constructor( - @Inject(NgControl) - @Optional() - private readonly ngControl: NgControl, - ) {} +export class DisableControl { + private readonly ngControl = inject(NgControl, {optional: true})!; - @Input() - public set disableControl(condition: boolean) { - const action: string = condition ? 'disable' : 'enable'; + constructor() { + this.setControlStateOnInputChange(); + } + + private setControlStateOnInputChange() { + effect(() => { + const disable = this.disableControl(); + const action: string = disable ? 'disable' : 'enable'; - (this.ngControl?.control as PlainObject)?.[action]?.(); + (this.ngControl?.control as PlainObject)?.[action]?.(); + }); } + + public readonly disableControl = input(false); } diff --git a/libs/cdk/directives/disable-control/disable-control.module.ts b/libs/cdk/directives/disable-control/disable-control.module.ts deleted file mode 100644 index 887254bf9..000000000 --- a/libs/cdk/directives/disable-control/disable-control.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; - -import {DisableControlDirective} from './disable-control.directive'; - -@NgModule({ - imports: [CommonModule], - declarations: [DisableControlDirective], - exports: [DisableControlDirective], -}) -export class DisableControlModule {} diff --git a/libs/cdk/directives/index.ts b/libs/cdk/directives/index.ts index 4996d5091..2a35c2a50 100644 --- a/libs/cdk/directives/index.ts +++ b/libs/cdk/directives/index.ts @@ -1,21 +1,13 @@ -export {AmountFormatDirective} from './amount-format/amount-format.directive'; -export {AmountFormatModule} from './amount-format/amount-format.module'; export { AMOUNT_FORMAT_OPTIONS, + AmountFormat, + AmountOptions, DEFAULT_AMOUNT_OPTIONS, -} from './amount-format/amount-format.properties'; -export {AmountOptions} from './amount-format/amount-options'; -export {ConvertCaseDirective} from './convert-case/convert-case.directive'; -export {ConvertCaseModule} from './convert-case/convert-case.module'; -export {DisableControlDirective} from './disable-control/disable-control.directive'; -export {DisableControlModule} from './disable-control/disable-control.module'; -export {InitialFocusDirective} from './initial-focus/initial-focus.directive'; -export {InitialFocusModule} from './initial-focus/initial-focus.module'; -export {InputFilterConfig} from './input-filter/input-filter.config'; -export {InputFilterDirective} from './input-filter/input-filter.directive'; -export {InputFilterModule} from './input-filter/input-filter.module'; -export {SplitStringDirective} from './split-string/split-string.directive'; -export {SplitStringModule} from './split-string/split-string.module'; -export {SplitStringOptions} from './split-string/split-string-options'; -export {TrimInputDirective} from './trim-input/trim-input.directive'; -export {TrimInputModule} from './trim-input/trim-input.module'; + provideAmountFormat, +} from './amount-format'; +export {ConvertCase} from './convert-case/convert-case.directive'; +export {DisableControl} from './disable-control/disable-control.directive'; +export {InitialFocus} from './initial-focus/initial-focus.directive'; +export {InputFilter, InputFilterConfig, provideInputFilter} from './input-filter'; +export {SplitString, SplitStringOptions} from './split-string'; +export {TrimInput} from './trim-input/trim-input.directive'; diff --git a/libs/cdk/directives/initial-focus/initial-focus.directive.ts b/libs/cdk/directives/initial-focus/initial-focus.directive.ts index fdf216e45..e54d38cbe 100644 --- a/libs/cdk/directives/initial-focus/initial-focus.directive.ts +++ b/libs/cdk/directives/initial-focus/initial-focus.directive.ts @@ -2,8 +2,8 @@ import { AfterViewInit, Directive, ElementRef, - Inject, - Input, + inject, + input, NgZone, OnDestroy, } from '@angular/core'; @@ -13,24 +13,16 @@ import {takeUntil} from 'rxjs/operators'; const MIN_DELAY = 500; @Directive({selector: '[initialFocus]'}) -export class InitialFocusDirective implements AfterViewInit, OnDestroy { +export class InitialFocus implements AfterViewInit, OnDestroy { + private readonly element = inject>(ElementRef); + private readonly ngZone = inject(NgZone); + private readonly className: string = 'initial-focused'; private readonly unsubscribe$: Subject = new Subject(); - @Input() - public focusDelay: number = MIN_DELAY; - - @Input() - public focusDisabled = false; - @Input() - public type?: string; - - constructor( - @Inject(ElementRef) - private readonly element: ElementRef, - @Inject(NgZone) - private readonly ngZone: NgZone, - ) {} + public readonly focusDelay = input(MIN_DELAY); + public readonly focusDisabled = input(false); + public readonly type = input(); private get el(): HTMLInputElement { return this.element.nativeElement; @@ -45,9 +37,9 @@ export class InitialFocusDirective implements AfterViewInit, OnDestroy { } private decideAndTryToFocus(): void { - if (!this.focusDisabled && !this.isFocused()) { + if (!this.focusDisabled() && !this.isFocused()) { this.ngZone.runOutsideAngular((): void => { - timer(this.focusDelay) + timer(this.focusDelay()) .pipe(takeUntil(this.unsubscribe$)) .subscribe((_value: number): void => { this.focus(); @@ -68,13 +60,15 @@ export class InitialFocusDirective implements AfterViewInit, OnDestroy { this.el.focus(); // selection range doesn't work with number type - if (this.type === 'number') { + const type = this.type(); + + if (type === 'number') { this.el.type = 'text'; } this.el.setSelectionRange(this.el.value.length, this.el.value.length); - if (this.type === 'number') { + if (type === 'number') { this.el.type = 'number'; } diff --git a/libs/cdk/directives/initial-focus/initial-focus.module.ts b/libs/cdk/directives/initial-focus/initial-focus.module.ts deleted file mode 100644 index c2a285bae..000000000 --- a/libs/cdk/directives/initial-focus/initial-focus.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; - -import {InitialFocusDirective} from './initial-focus.directive'; - -@NgModule({ - imports: [CommonModule], - declarations: [InitialFocusDirective], - exports: [InitialFocusDirective], -}) -export class InitialFocusModule {} diff --git a/libs/cdk/directives/input-filter/index.ts b/libs/cdk/directives/input-filter/index.ts new file mode 100644 index 000000000..dd3aef432 --- /dev/null +++ b/libs/cdk/directives/input-filter/index.ts @@ -0,0 +1,3 @@ +export * from './input-filter.config'; +export * from './input-filter.directive'; +export * from './input-filter.provider'; diff --git a/libs/cdk/directives/input-filter/input-filter.directive.ts b/libs/cdk/directives/input-filter/input-filter.directive.ts index 2e99e79ae..f9817dbf7 100644 --- a/libs/cdk/directives/input-filter/input-filter.directive.ts +++ b/libs/cdk/directives/input-filter/input-filter.directive.ts @@ -1,11 +1,4 @@ -import { - Directive, - ElementRef, - HostListener, - Inject, - Input, - Optional, -} from '@angular/core'; +import {Directive, ElementRef, HostListener, inject, input} from '@angular/core'; import {hasItems} from '@angular-ru/cdk/array'; import {ControlValueInterceptor} from '@angular-ru/cdk/forms'; import {filter, FilterPredicate} from '@angular-ru/cdk/string'; @@ -18,25 +11,20 @@ import {InputFilterConfig} from './input-filter.config'; selector: '[inputFilter]', providers: [ControlValueInterceptor], }) -export class InputFilterDirective { - private manualEvent: Nullable = null; - @Input() - public declare inputFilter: FilterPredicate | ''; +export class InputFilter { + private readonly elementRef = inject>(ElementRef); + private readonly config = inject>(InputFilterConfig, { + optional: true, + })!; - @Input() - public filterDisabled = false; + private manualEvent: Nullable = null; - constructor( - @Inject(ElementRef) - private readonly elementRef: ElementRef, - @Optional() - @Inject(InputFilterConfig) - private readonly config: Nullable, - ) {} + public readonly inputFilter = input(''); + public readonly filterDisabled = input(false); @HostListener('input', ['$event']) public onInput(baseEvent: InputEvent): void { - if (this.filterDisabled || this.manualEvent === baseEvent) { + if (this.filterDisabled() || this.manualEvent === baseEvent) { return; } @@ -54,10 +42,11 @@ export class InputFilterDirective { } private getPredicate(): FilterPredicate | '' { - const isInputPredicate: boolean = Array.isArray(this.inputFilter) - ? hasItems(this.inputFilter) - : checkValueIsFilled(this.inputFilter); + const inputFilter = this.inputFilter(); + const isInputPredicate: boolean = Array.isArray(inputFilter) + ? hasItems(inputFilter) + : checkValueIsFilled(inputFilter); - return isInputPredicate ? this.inputFilter : this.config?.default ?? []; + return isInputPredicate ? inputFilter : (this.config?.default ?? []); } } diff --git a/libs/cdk/directives/input-filter/input-filter.module.ts b/libs/cdk/directives/input-filter/input-filter.module.ts deleted file mode 100644 index 84c5e4c0b..000000000 --- a/libs/cdk/directives/input-filter/input-filter.module.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {ModuleWithProviders, NgModule} from '@angular/core'; -import {Nullable} from '@angular-ru/cdk/typings'; - -import {InputFilterConfig} from './input-filter.config'; -import {InputFilterDirective} from './input-filter.directive'; - -@NgModule({ - imports: [CommonModule], - declarations: [InputFilterDirective], - exports: [InputFilterDirective], -}) -export class InputFilterModule { - public static forRoot( - config: Nullable>, - ): ModuleWithProviders { - return { - ngModule: InputFilterModule, - providers: [ - { - provide: InputFilterConfig, - useValue: config, - }, - ], - }; - } - - public static forChild( - config: Nullable>, - ): ModuleWithProviders { - return { - ngModule: InputFilterModule, - providers: [ - { - provide: InputFilterConfig, - useValue: config, - }, - ], - }; - } -} diff --git a/libs/cdk/directives/input-filter/input-filter.provider.ts b/libs/cdk/directives/input-filter/input-filter.provider.ts new file mode 100644 index 000000000..91a136605 --- /dev/null +++ b/libs/cdk/directives/input-filter/input-filter.provider.ts @@ -0,0 +1,13 @@ +import {makeEnvironmentProviders} from '@angular/core'; +import {Nullable} from '@angular-ru/cdk/typings'; + +import {InputFilterConfig} from './input-filter.config'; + +export function provideInputFilter(config: Nullable>) { + return makeEnvironmentProviders([ + { + provide: InputFilterConfig, + useValue: config, + }, + ]); +} diff --git a/libs/cdk/directives/split-string/index.ts b/libs/cdk/directives/split-string/index.ts new file mode 100644 index 000000000..8e76395da --- /dev/null +++ b/libs/cdk/directives/split-string/index.ts @@ -0,0 +1,2 @@ +export * from './split-string.directive'; +export * from './split-string-options'; diff --git a/libs/cdk/directives/split-string/split-string.directive.ts b/libs/cdk/directives/split-string/split-string.directive.ts index 8ec9765f1..fad665137 100644 --- a/libs/cdk/directives/split-string/split-string.directive.ts +++ b/libs/cdk/directives/split-string/split-string.directive.ts @@ -1,4 +1,4 @@ -import {Directive, Input} from '@angular/core'; +import {Directive, inject, input} from '@angular/core'; import {ControlValueInterceptor} from '@angular-ru/cdk/forms'; import {trim} from '@angular-ru/cdk/string'; import {checkValueIsFilled} from '@angular-ru/cdk/utils'; @@ -9,23 +9,24 @@ import {SplitStringOptions} from './split-string-options'; selector: '[splitString]', providers: [ControlValueInterceptor], }) -export class SplitStringDirective { +export class SplitString { private readonly defaultSplitOptions: SplitStringOptions = { separator: /[\n,;]/g, joinWith: ', ', }; - @Input() - public splitOptions?: Partial; + public readonly splitOptions = input>(); + + constructor() { + const interceptor = inject(ControlValueInterceptor); - constructor(interceptor: ControlValueInterceptor) { interceptor.attach({ toModelValue: (viewValue: string): string[] => this.splitAndTrimViewValue(viewValue), toViewValue: (modelValue: string[] | string): string => Array.isArray(modelValue) ? modelValue.join( - this.splitOptions?.joinWith ?? + this.splitOptions()?.joinWith ?? this.defaultSplitOptions.joinWith, ) : modelValue, @@ -34,7 +35,7 @@ export class SplitStringDirective { private splitAndTrimViewValue(viewValue: string): string[] { const separator: RegExp | string = - this.splitOptions?.separator ?? this.defaultSplitOptions.separator; + this.splitOptions()?.separator ?? this.defaultSplitOptions.separator; return viewValue .split(separator) diff --git a/libs/cdk/directives/split-string/split-string.module.ts b/libs/cdk/directives/split-string/split-string.module.ts deleted file mode 100644 index 9502b1847..000000000 --- a/libs/cdk/directives/split-string/split-string.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; - -import {SplitStringDirective} from './split-string.directive'; - -@NgModule({ - imports: [CommonModule], - declarations: [SplitStringDirective], - exports: [SplitStringDirective], -}) -export class SplitStringModule {} diff --git a/libs/cdk/directives/trim-input/trim-input.directive.ts b/libs/cdk/directives/trim-input/trim-input.directive.ts index 4d1a5f496..1ee8d987b 100644 --- a/libs/cdk/directives/trim-input/trim-input.directive.ts +++ b/libs/cdk/directives/trim-input/trim-input.directive.ts @@ -1,35 +1,42 @@ import { Directive, + effect, ElementRef, HostListener, - Inject, - Input, + inject, + input, OnInit, - Optional, } from '@angular/core'; import {AbstractControl, NgControl} from '@angular/forms'; import {Nullable} from '@angular-ru/cdk/typings'; import {isNotNil} from '@angular-ru/cdk/utils'; @Directive({selector: '[trimInput]'}) -export class TrimInputDirective implements OnInit { - private declare name: string; - private declare previousName: string; +export class TrimInput implements OnInit { + public readonly elementRef = inject(ElementRef); + private readonly ngControl = inject(NgControl, {optional: true}); + + declare private name?: string; + declare private previousName?: string; private previousValue: any; - @Input() - public trimDisabled = false; + public readonly trimDisabled = input(false); + public readonly formControlName = input(); + + constructor() { + this.listenToFormControlNameChanges(); + } - constructor( - @Inject(ElementRef) public readonly elementRef: ElementRef, - @Inject(NgControl) @Optional() private readonly ngControl?: NgControl, - ) {} + private listenToFormControlNameChanges() { + effect(() => { + const name = this.formControlName(); - @Input() - public set formControlName(name: string) { - this.previousValue = this.ngControl?.control?.parent?.get(this.name)?.value; - this.previousName = this.name; - this.name = name; + this.previousValue = this.ngControl?.control?.parent?.get( + this.name ?? '', + )?.value; + this.previousName = this.name; + this.name = name; + }); } @HostListener('keydown.enter') @@ -47,7 +54,7 @@ export class TrimInputDirective implements OnInit { } private trimValue(): void { - if (this.trimDisabled) { + if (this.trimDisabled()) { return; } @@ -56,8 +63,8 @@ export class TrimInputDirective implements OnInit { .trim(); const control: Nullable = this.ngControl?.control?.parent - ? this.ngControl?.control?.parent?.get(this.name) - : this.ngControl?.control?.get(this.name); + ? this.ngControl?.control?.parent?.get(this.name ?? '') + : this.ngControl?.control?.get(this.name ?? ''); if (isNotNil(control)) { const modelValue: string = (this.ngControl?.value ?? control?.value) @@ -69,7 +76,7 @@ export class TrimInputDirective implements OnInit { } else { control?.setValue(modelValue, {emitEvent: false}); control?.parent - ?.get(this.previousName) + ?.get(this.previousName ?? '') ?.setValue(this.previousValue, {emitEvent: false}); } } diff --git a/libs/cdk/directives/trim-input/trim-input.module.ts b/libs/cdk/directives/trim-input/trim-input.module.ts deleted file mode 100644 index 268651c2b..000000000 --- a/libs/cdk/directives/trim-input/trim-input.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; - -import {TrimInputDirective} from './trim-input.directive'; - -@NgModule({ - imports: [CommonModule], - declarations: [TrimInputDirective], - exports: [TrimInputDirective], -}) -export class TrimInputModule {} diff --git a/libs/cdk/entity/typings/entity-types.ts b/libs/cdk/entity/typings/entity-types.ts index 41d0e43af..b66ddc535 100644 --- a/libs/cdk/entity/typings/entity-types.ts +++ b/libs/cdk/entity/typings/entity-types.ts @@ -1,15 +1,11 @@ export type EntityIdType = number | string; -export type EntityDictionary = { - [key in K]: V; -}; +export type EntityDictionary = Record; -export type EmptyDictionary = { - [key in K]?: V; -}; +export type EmptyDictionary = Partial>; export type EntityStateValue = T | ((state: T) => T); export type EntityPatchValue = Partial; -export type KeysDictionary = {[key in K]?: K}; +export type KeysDictionary = Partial>; diff --git a/libs/cdk/excel/excel-builder.module.ts b/libs/cdk/excel/excel-builder.module.ts deleted file mode 100644 index 4654079a5..000000000 --- a/libs/cdk/excel/excel-builder.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {ModuleWithProviders, NgModule} from '@angular/core'; -import {PlainTableComposerModule} from '@angular-ru/cdk/table-utils'; -import {WebWorkerThreadService} from '@angular-ru/cdk/webworker'; - -import {ExcelService} from './excel.service'; -import {ExcelBuilderService} from './excel-builder.service'; -import {ExcelBuilderDefaultTextColumnInterceptor} from './excel-builder-default-text-column-interceptor.service'; -import {EXCEL_BUILDER_INTERCEPTOR_TOKEN} from './excel-interceptor-text.token'; - -@NgModule({ - imports: [PlainTableComposerModule.forRoot()], - providers: [WebWorkerThreadService], -}) -export class ExcelBuilderModule { - public static forRoot(): ModuleWithProviders { - return { - ngModule: ExcelBuilderModule, - providers: [ - ExcelService, - ExcelBuilderService, - { - provide: EXCEL_BUILDER_INTERCEPTOR_TOKEN, - useClass: ExcelBuilderDefaultTextColumnInterceptor, - }, - ], - }; - } -} diff --git a/libs/cdk/excel/excel-builder.provider.ts b/libs/cdk/excel/excel-builder.provider.ts new file mode 100644 index 000000000..b70fc4a3f --- /dev/null +++ b/libs/cdk/excel/excel-builder.provider.ts @@ -0,0 +1,21 @@ +import {makeEnvironmentProviders} from '@angular/core'; +import {providePlainTableComposer} from '@angular-ru/cdk/table-utils'; +import {WebWorkerThreadService} from '@angular-ru/cdk/webworker'; + +import {ExcelService} from './excel.service'; +import {ExcelBuilderService} from './excel-builder.service'; +import {ExcelBuilderDefaultTextColumnInterceptor} from './excel-builder-default-text-column-interceptor.service'; +import {EXCEL_BUILDER_INTERCEPTOR_TOKEN} from './excel-interceptor-text.token'; + +export function provideExcelBuilder() { + return makeEnvironmentProviders([ + ExcelService, + ExcelBuilderService, + { + provide: EXCEL_BUILDER_INTERCEPTOR_TOKEN, + useClass: ExcelBuilderDefaultTextColumnInterceptor, + }, + WebWorkerThreadService, + providePlainTableComposer(), + ]); +} diff --git a/libs/cdk/excel/excel-builder.service.ts b/libs/cdk/excel/excel-builder.service.ts index a17315b01..1bee0c7a4 100644 --- a/libs/cdk/excel/excel-builder.service.ts +++ b/libs/cdk/excel/excel-builder.service.ts @@ -1,6 +1,6 @@ /* eslint-disable unicorn/consistent-function-scoping */ // eslint-disable-next-line max-classes-per-file -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {toFormatDateTime} from '@angular-ru/cdk/date'; import {PlainTableComposerService} from '@angular-ru/cdk/table-utils'; import {EmptyValue, Nullable, PlainObject} from '@angular-ru/cdk/typings'; @@ -32,10 +32,8 @@ const enum StyleType { @Injectable() export class ExcelBuilderService { - constructor( - public plainTableComposer: PlainTableComposerService, - public webWorker: WebWorkerThreadService, - ) {} + public plainTableComposer = inject(PlainTableComposerService); + public webWorker = inject(WebWorkerThreadService); private static downloadWorkbook(blob: Blob, workbookName: string): void { downloadFile({ @@ -57,7 +55,7 @@ export class ExcelBuilderService { const nextValue: any = typeof value === 'string' ? value.trim() : value; - return [undefined, null, NaN, '', Infinity].includes(nextValue); + return ['', Infinity, NaN, null, undefined].includes(nextValue); } class ExcelBuilder { @@ -87,7 +85,7 @@ export class ExcelBuilderService { ${ExcelBuilder.commonBorderStyles}