├── src
├── assets
│ └── .gitkeep
├── app
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app-routing.module.ts
│ ├── app.module.ts
│ └── app.component.spec.ts
├── favicon.ico
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── styles.scss
├── index.html
├── main.ts
├── test.ts
└── polyfills.ts
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── projects
├── contenteditable
│ ├── src
│ │ ├── public-api.ts
│ │ ├── lib
│ │ │ ├── ngs-contenteditable.module.ts
│ │ │ └── editable.directive.ts
│ │ └── test.ts
│ ├── ng-package.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.lib.json
│ ├── .browserslistrc
│ ├── package.json
│ ├── karma.conf.js
│ ├── CHANGELOG.md
│ └── README.md
├── forms
│ ├── ng-package.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.lib.json
│ ├── src
│ │ ├── lib
│ │ │ ├── ngs-forms.module.ts
│ │ │ ├── assert.ts
│ │ │ ├── form-array.spec.ts
│ │ │ ├── input-file.directive.ts
│ │ │ ├── assert.spec.ts
│ │ │ ├── form-builder.spec.ts
│ │ │ ├── form-builder.ts
│ │ │ ├── validators.spec.ts
│ │ │ ├── form-control.ts
│ │ │ ├── types.spec.ts
│ │ │ ├── types.ts
│ │ │ ├── form-control.spec.ts
│ │ │ ├── validators.ts
│ │ │ ├── form-array.ts
│ │ │ └── form-group.ts
│ │ ├── public-api.ts
│ │ └── test.ts
│ ├── .browserslistrc
│ ├── package.json
│ ├── karma.conf.js
│ ├── CHANGELOG.md
│ └── README.md
└── api-mock
│ ├── ng-package.json
│ ├── package.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.lib.json
│ ├── src
│ ├── public-api.ts
│ ├── test.ts
│ └── lib
│ │ ├── api-mock.module.ts
│ │ ├── pick-properties.ts
│ │ ├── pick-properties.spec.ts
│ │ └── types.ts
│ ├── .browserslistrc
│ ├── CHANGELOG.md
│ ├── karma.conf.js
│ └── README.md
├── .editorconfig
├── tsconfig.app.json
├── tsconfig.spec.json
├── .browserslistrc
├── README.md
├── .github
└── FUNDING.yml
├── .gitignore
├── package.json
├── tsconfig.json
├── karma.conf.js
└── angular.json
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KostyaTretyak/ng-stack/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/projects/contenteditable/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of contenteditable
3 | */
4 |
5 | export * from './lib/editable.directive';
6 | export * from './lib/ngs-contenteditable.module';
7 |
--------------------------------------------------------------------------------
/projects/forms/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/forms",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/projects/api-mock/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/api-mock",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/projects/contenteditable/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/contenteditable",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.scss']
7 | })
8 | export class AppComponent {
9 | title = 'ng-stack-13';
10 | }
11 |
--------------------------------------------------------------------------------
/projects/api-mock/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-stack/api-mock",
3 | "version": "1.4.1",
4 | "peerDependencies": {
5 | "@angular/common": ">=4.3.6 <14.0.0",
6 | "@angular/core": ">=4.3.6 <14.0.0",
7 | "rxjs": ">=6.0.0"
8 | },
9 | "dependencies": {
10 | "tslib": "^2.3.0"
11 | }
12 | }
--------------------------------------------------------------------------------
/projects/contenteditable/src/lib/ngs-contenteditable.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { EditableDirective } from './editable.directive';
3 |
4 | @NgModule({
5 | declarations: [EditableDirective],
6 | exports: [EditableDirective],
7 | })
8 | export class NgsContenteditableModule {}
9 |
--------------------------------------------------------------------------------
/projects/forms/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.lib.json",
4 | "compilerOptions": {
5 | "declarationMap": false
6 | },
7 | "angularCompilerOptions": {
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | const routes: Routes = [];
5 |
6 | @NgModule({
7 | imports: [RouterModule.forRoot(routes)],
8 | exports: [RouterModule]
9 | })
10 | export class AppRoutingModule { }
11 |
--------------------------------------------------------------------------------
/projects/api-mock/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.lib.json",
4 | "compilerOptions": {
5 | "declarationMap": false
6 | },
7 | "angularCompilerOptions": {
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/projects/contenteditable/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.lib.json",
4 | "compilerOptions": {
5 | "declarationMap": false
6 | },
7 | "angularCompilerOptions": {
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NgStack13
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/projects/forms/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/projects/api-mock/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/projects/contenteditable/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/projects/forms/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/lib",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "inlineSources": true,
9 | "types": []
10 | },
11 | "exclude": [
12 | "src/test.ts",
13 | "**/*.spec.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/projects/api-mock/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/lib",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "inlineSources": true,
9 | "types": []
10 | },
11 | "exclude": [
12 | "src/test.ts",
13 | "**/*.spec.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/projects/contenteditable/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/lib",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "inlineSources": true,
9 | "types": []
10 | },
11 | "exclude": [
12 | "src/test.ts",
13 | "**/*.spec.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/ngs-forms.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 |
4 | import { FormBuilder } from './form-builder';
5 | import { InputFileDirective } from './input-file.directive';
6 |
7 | @NgModule({
8 | declarations: [InputFileDirective],
9 | exports: [ReactiveFormsModule, InputFileDirective],
10 | providers: [FormBuilder],
11 | })
12 | export class NgsFormsModule {}
13 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { AppRoutingModule } from './app-routing.module';
5 | import { AppComponent } from './app.component';
6 |
7 | @NgModule({
8 | declarations: [
9 | AppComponent
10 | ],
11 | imports: [
12 | BrowserModule,
13 | AppRoutingModule
14 | ],
15 | providers: [],
16 | bootstrap: [AppComponent]
17 | })
18 | export class AppModule { }
19 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "pwa-chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/projects/api-mock/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of api-mock
3 | */
4 |
5 | export * from './lib/api-mock.module';
6 | export * from './lib/http-status-codes';
7 | export * from './lib/http-backend.service';
8 | export * from './lib/pick-properties';
9 | export {
10 | ApiMockService,
11 | ApiMockConfig,
12 | ApiMockRootRoute,
13 | ApiMockRoute,
14 | ApiMockDataCallback,
15 | ApiMockResponseCallback,
16 | ObjectAny,
17 | CallbackAny,
18 | HttpMethod,
19 | ApiMockDataCallbackOptions,
20 | ApiMockResponseCallbackOptions,
21 | } from './lib/types';
22 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 |
--------------------------------------------------------------------------------
/projects/forms/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of forms
3 | */
4 | export { NgsFormsModule } from './lib/ngs-forms.module';
5 | export { FormArray } from './lib/form-array';
6 | export { FormBuilder } from './lib/form-builder';
7 | export { FormControl } from './lib/form-control';
8 | export { FormGroup } from './lib/form-group';
9 | export { Validators } from './lib/validators';
10 | export { InputFileDirective } from './lib/input-file.directive';
11 | export {
12 | Status,
13 | ValidatorFn,
14 | AsyncValidatorFn,
15 | ValidationErrors,
16 | AbstractControlOptions,
17 | ValidatorsModel,
18 | Control,
19 | ExtractModelValue,
20 | } from './lib/types';
21 |
--------------------------------------------------------------------------------
/projects/forms/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 |
--------------------------------------------------------------------------------
/projects/api-mock/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 |
--------------------------------------------------------------------------------
/projects/contenteditable/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @ng-stack
2 |
3 | This Angular library contains two projects:
4 | - [@ng-stack/forms](./projects/forms) - provides wrapped Angular's Reactive Forms to write its more strongly typed.
5 | - [@ng-stack/contenteditable](./projects/contenteditable) - a micro Angular v4+ contenteditable directive for integration with Angular forms. It just implements [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor) for this purpose.
6 |
7 | ## Related resources
8 |
9 | If you love the architectural concepts of Angular and are interested in a backend framework that is very similar to Angular, you can also check out the [Ditsmod](https://ditsmod.github.io/en/) - new Node.js framework, written in TypeScript.
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: ng-stack
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/projects/contenteditable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-stack/contenteditable",
3 | "version": "2.0.1",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/KostyaTretyak/ng-stack/tree/master/projects/contenteditable"
7 | },
8 | "author": {
9 | "name": "Костя Третяк",
10 | "email": "ktretiak.in.ua@gmail.com"
11 | },
12 | "keywords": [
13 | "angular",
14 | "contenteditable"
15 | ],
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/KostyaTretyak/ng-stack/issues?q=label%3A%22project%3A+contenteditable%22"
19 | },
20 | "peerDependencies": {
21 | "@angular/core": ">=4.0.0"
22 | },
23 | "dependencies": {
24 | "tslib": "^2.3.0"
25 | }
26 | }
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/projects/forms/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-stack/forms",
3 | "version": "3.1.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/KostyaTretyak/ng-stack/tree/master/projects/forms"
7 | },
8 | "author": {
9 | "name": "Костя Третяк",
10 | "email": "ktretiak.in.ua@gmail.com"
11 | },
12 | "keywords": [
13 | "angular",
14 | "forms"
15 | ],
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/KostyaTretyak/ng-stack/issues?q=label%3A%22project%3A+forms%22"
19 | },
20 | "peerDependencies": {
21 | "@angular/core": ">=13.0.0 < 15.0.0",
22 | "@angular/forms": ">=13.0.0 < 15.0.0",
23 | "typescript": "^4.5.2"
24 | },
25 | "dependencies": {
26 | "tslib": "^2.3.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 |
16 | # IDEs and editors
17 | /.idea
18 | .project
19 | .classpath
20 | .c9/
21 | *.launch
22 | .settings/
23 | *.sublime-workspace
24 |
25 | # IDE - VSCode
26 | .vscode/*
27 | !.vscode/settings.json
28 | !.vscode/tasks.json
29 | !.vscode/launch.json
30 | !.vscode/extensions.json
31 | .history/*
32 |
33 | # misc
34 | /.angular/cache
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: {
11 | context(path: string, deep?: boolean, filter?: RegExp): {
12 | (id: string): T;
13 | keys(): string[];
14 | };
15 | };
16 |
17 | // First, initialize the Angular testing environment.
18 | getTestBed().initTestEnvironment(
19 | BrowserDynamicTestingModule,
20 | platformBrowserDynamicTesting(),
21 | );
22 |
23 | // Then we find all the tests.
24 | const context = require.context('./', true, /\.spec\.ts$/);
25 | // And load the modules.
26 | context.keys().map(context);
27 |
--------------------------------------------------------------------------------
/projects/api-mock/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | declare const require: {
12 | context(path: string, deep?: boolean, filter?: RegExp): {
13 | (id: string): T;
14 | keys(): string[];
15 | };
16 | };
17 |
18 | // First, initialize the Angular testing environment.
19 | getTestBed().initTestEnvironment(
20 | BrowserDynamicTestingModule,
21 | platformBrowserDynamicTesting(),
22 | );
23 |
24 | // Then we find all the tests.
25 | const context = require.context('./', true, /\.spec\.ts$/);
26 | // And load the modules.
27 | context.keys().map(context);
28 |
--------------------------------------------------------------------------------
/projects/forms/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | declare const require: {
12 | context(path: string, deep?: boolean, filter?: RegExp): {
13 | (id: string): T;
14 | keys(): string[];
15 | };
16 | };
17 |
18 | // First, initialize the Angular testing environment.
19 | getTestBed().initTestEnvironment(
20 | BrowserDynamicTestingModule,
21 | platformBrowserDynamicTesting(),
22 | );
23 |
24 | // Then we find all the tests.
25 | const context = require.context('./', true, /\.spec\.ts$/);
26 | // And load the modules.
27 | context.keys().map(context);
28 |
--------------------------------------------------------------------------------
/projects/contenteditable/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | declare const require: {
12 | context(path: string, deep?: boolean, filter?: RegExp): {
13 | (id: string): T;
14 | keys(): string[];
15 | };
16 | };
17 |
18 | // First, initialize the Angular testing environment.
19 | getTestBed().initTestEnvironment(
20 | BrowserDynamicTestingModule,
21 | platformBrowserDynamicTesting(),
22 | );
23 |
24 | // Then we find all the tests.
25 | const context = require.context('./', true, /\.spec\.ts$/);
26 | // And load the modules.
27 | context.keys().map(context);
28 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async () => {
7 | await TestBed.configureTestingModule({
8 | imports: [
9 | RouterTestingModule
10 | ],
11 | declarations: [
12 | AppComponent
13 | ],
14 | }).compileComponents();
15 | });
16 |
17 | it('should create the app', () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.componentInstance;
20 | expect(app).toBeTruthy();
21 | });
22 |
23 | it(`should have as title 'ng-stack-13'`, () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | const app = fixture.componentInstance;
26 | expect(app.title).toEqual('ng-stack-13');
27 | });
28 |
29 | it('should render title', () => {
30 | const fixture = TestBed.createComponent(AppComponent);
31 | fixture.detectChanges();
32 | const compiled = fixture.nativeElement as HTMLElement;
33 | expect(compiled.querySelector('.content span')?.textContent).toContain('ng-stack-13 app is running!');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/projects/api-mock/src/lib/api-mock.module.ts:
--------------------------------------------------------------------------------
1 | import { HttpBackend } from '@angular/common/http';
2 | import { ModuleWithProviders, NgModule, Type } from '@angular/core';
3 |
4 | import { HttpBackendService } from './http-backend.service';
5 | import { ApiMockConfig, ApiMockService } from './types';
6 |
7 | @NgModule()
8 | export class ApiMockModule {
9 | static forRoot(
10 | apiMockService: Type,
11 | apiMockConfig?: ApiMockConfig
12 | ): ModuleWithProviders {
13 | return {
14 | ngModule: ApiMockModule,
15 | providers: [
16 | { provide: ApiMockService, useClass: apiMockService },
17 | { provide: ApiMockConfig, useValue: apiMockConfig },
18 | { provide: HttpBackend, useClass: HttpBackendService },
19 | ],
20 | };
21 | }
22 |
23 | /**
24 | * Enable and configure the `@ng-stack/api-mock` in a lazy-loaded feature module.
25 | * Same as `forRoot`.
26 | * This is a feel-good method so you can follow the Angular style guide for lazy-loaded modules.
27 | */
28 | static forFeature(
29 | apiMockService: Type,
30 | apiMockConfig?: ApiMockConfig
31 | ): ModuleWithProviders {
32 | return ApiMockModule.forRoot(apiMockService, apiMockConfig);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-stack-13",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "14.2.0",
14 | "@angular/common": "14.2.0",
15 | "@angular/compiler": "14.2.0",
16 | "@angular/core": "14.2.0",
17 | "@angular/forms": "14.2.0",
18 | "@angular/platform-browser": "14.2.0",
19 | "@angular/platform-browser-dynamic": "14.2.0",
20 | "@angular/router": "14.2.0",
21 | "rxjs": "~7.4.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.11.4"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "14.2.0",
27 | "@angular/cli": "14.2.0",
28 | "@angular/compiler-cli": "14.2.0",
29 | "@types/assert-plus": "^1.0.4",
30 | "@types/jasmine": "~3.10.0",
31 | "@types/node": "^12.11.1",
32 | "assert-plus": "^1.0.0",
33 | "jasmine-core": "~3.10.0",
34 | "karma": "~6.3.0",
35 | "karma-chrome-launcher": "~3.1.0",
36 | "karma-coverage": "~2.1.0",
37 | "karma-jasmine": "~4.0.0",
38 | "karma-jasmine-html-reporter": "~1.7.0",
39 | "ng-packagr": "14.2.0",
40 | "typescript": "4.8.2"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/projects/api-mock/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # [1.3.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/api-mock%401.3.0) (2020-06-25)
3 |
4 | ### Features
5 |
6 | * **peer dependencies:** added support for Angular v10.
7 |
8 |
9 | ## [1.2.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/api-mock%401.2.1) (2020-04-23)
10 |
11 | ### Bug Fixes
12 |
13 | * **queryParams:** fixed issue which incorrectly parses the absolute URL.
14 |
15 |
16 | ## [1.2.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/api-mock%401.2.0) (2020-04-12)
17 |
18 | ### Features
19 |
20 | * **ApiMockDataCallbackOptions:** Added `reqHeaders` [#65](https://github.com/KostyaTretyak/ng-stack/issues/65).
21 |
22 |
23 | ## [1.1.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/api-mock%401.1.0) (2020-04-08)
24 |
25 | ### Features
26 |
27 | * **ApiMockConfig:** Not need to set `*NoAction` option to have a successful response for routes without `dataCallback`.
28 | Now this functionality is "by default". See [this commit](https://github.com/KostyaTretyak/ng-stack/commit/269be2d).
29 |
30 |
31 | ## 0.0.0-beta.1 (2020-03-17)
32 |
33 | ### Features
34 |
35 | * **npm pack:** `@ng-stack/api-mock` use Angular-CLI v9 git mono repository and build npm pack with it.
36 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "strictPropertyInitialization": false,
11 | "paths": {
12 | "@ng-stack/forms": [
13 | "dist/forms/forms",
14 | "dist/forms"
15 | ],
16 | "@ng-stack/contenteditable": [
17 | "dist/contenteditable/contenteditable",
18 | "dist/contenteditable"
19 | ],
20 | "@ng-stack/api-mock": [
21 | "dist/api-mock/api-mock",
22 | "dist/api-mock"
23 | ]
24 | },
25 | "noPropertyAccessFromIndexSignature": true,
26 | "noImplicitReturns": true,
27 | "noFallthroughCasesInSwitch": true,
28 | "sourceMap": true,
29 | "declaration": false,
30 | "downlevelIteration": true,
31 | "experimentalDecorators": true,
32 | "moduleResolution": "node",
33 | "importHelpers": true,
34 | "target": "es2017",
35 | "module": "es2020",
36 | "lib": [
37 | "es2020",
38 | "dom"
39 | ]
40 | },
41 | "angularCompilerOptions": {
42 | "enableI18nLegacyMessageIdFormat": false,
43 | "strictInjectionParameters": true,
44 | "strictInputAccessModifiers": true,
45 | "strictTemplates": true
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/assert.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert-plus';
2 |
3 | export type Diff = T extends X ? never : T;
4 |
5 | export type hasType = Diff> & U;
6 | export type Fn = (...args: any[]) => any;
7 | export type NonFn = Diff;
8 | export type IsArray = T extends (infer Item)[] ? Item : never;
9 | export type NonArray = Diff>;
10 |
11 | // A bit rewrited TypeScript definitions for the type checking functions.
12 |
13 | export function isString(value: hasType) {
14 | assert.string(value);
15 | }
16 |
17 | export function isNumber(value: hasType) {
18 | assert.number(value);
19 | }
20 |
21 | export function isBoolean(value: hasType) {
22 | assert.bool(value);
23 | }
24 |
25 | export function isFunction(value: hasType) {
26 | assert.func(value);
27 | }
28 |
29 | export function isArray(value: hasType) {
30 | assert.array(value);
31 | }
32 |
33 | export function isObject(value: hasType & NonArray, object>) {
34 | assert.object(value);
35 | }
36 |
37 | export function isSymbol(value: hasType) {
38 | if (typeof value != 'symbol') {
39 | throw new TypeError(`${typeof value} (symbol) is required`);
40 | }
41 | }
42 |
43 | export function isNever(value: never) {
44 | if (value !== undefined) {
45 | throw new TypeError(`${typeof value} (undefined) is required`);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, './coverage/ng-stack-13'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/projects/forms/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, '../../coverage/forms'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/projects/api-mock/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, '../../coverage/api-mock'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/projects/contenteditable/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, '../../coverage/contenteditable'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/projects/contenteditable/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # [2.0.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/contenteditable-2.0.0) (2022-02-17)
3 |
4 | ### Breaking Changes
5 |
6 | - Changed module name to `NgsContenteditableModule`.
7 |
8 |
9 | ## [2.0.0-beta.2](https://github.com/KostyaTretyak/ng-stack/releases/tag/contenteditable-2.0.0-beta.2) (2022-02-14)
10 |
11 | ### Breaking Changes
12 |
13 | - Changed selector to `editable`.
14 | - The lib builded with Ivy Renderer.
15 |
16 |
17 | ## [1.1.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/contenteditable%401.1.1) (2020-10-15)
18 |
19 | ### Fix
20 |
21 | - Refactoring of the method to support the `unformattedPaste` attribute.
22 |
23 |
24 | ## [1.1.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/contenteditable%401.1.0) (2020-08-22)
25 |
26 | ### Features
27 |
28 | - Added experimental `[unformattedPaste]` attribute that allow copy formated text (from anywhere) and paste unformated text into HTML element with `contenteditable="true"` attribute.
29 |
30 |
31 | ## [1.0.2](https://github.com/KostyaTretyak/ng-stack/releases/tag/contenteditable%401.0.2) (2019-05-04)
32 |
33 | ### Fix
34 |
35 | - **directive selector:** Specified selector for directive:
36 |
37 | before:
38 |
39 | ```ts
40 | @Directive({
41 | selector: '[contenteditable]',
42 | //...
43 | })
44 | ```
45 |
46 | now:
47 |
48 | ```ts
49 | @Directive({
50 | selector: '[contenteditable][formControlName],[contenteditable][formControl],[contenteditable][ngModel]',
51 | //...
52 | })
53 | ```
54 |
55 |
56 | ## 1.0.0 (2019-02-06)
57 |
58 | ### Features
59 |
60 | * **npm pack:** `@ng-stack/contenteditable` use Angular-CLI v7 git mono repository and build npm pack with it.
61 | * **testing:** Added Unit tests.
62 | * **contenteditable:** Since version 1.0.0, `@ng-stack/contenteditable` accepts `contenteditable` as @Input property. ([#12](https://github.com/KostyaTretyak/ng-contenteditable/issues/12))
63 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including
12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js'; // Included with Angular CLI.
49 |
50 |
51 | /***************************************************************************************************
52 | * APPLICATION IMPORTS
53 | */
54 |
--------------------------------------------------------------------------------
/projects/api-mock/src/lib/pick-properties.ts:
--------------------------------------------------------------------------------
1 | import { ObjectAny } from './types';
2 |
3 | export function pickProperties>(targetObject: T, ...sourceObjects: S[]) {
4 | sourceObjects.forEach(sourceObj => {
5 | sourceObj = (sourceObj || {}) as S;
6 | for (const prop in targetObject) {
7 | if (Array.isArray(sourceObj[prop])) {
8 | targetObject[prop] = sourceObj[prop].slice();
9 | } else if (sourceObj[prop] !== undefined) {
10 | targetObject[prop] = sourceObj[prop] as any;
11 | }
12 | }
13 | });
14 |
15 | return targetObject;
16 | }
17 |
18 | /**
19 | * Pick all properties from a `targetObject` and replace
20 | * them with getters that takes values from corresponding properties of `sourceObjects`.
21 | * This is symplified version of `pickPropertiesAsGetters()`.
22 | *
23 | * If one of `sourceObjects` is equal to `targetObject`,
24 | * from start the function will do this:
25 | *
26 | ```ts
27 | targetObject = JSON.parse(JSON.stringify(targetObject));
28 | ```
29 | */
30 | export function pickAllPropertiesAsGetters(targetObject: T, ...sourceObjects: ObjectAny[]) {
31 | if (sourceObjects.length) {
32 | return pickPropertiesAsGetters(targetObject, {}, ...sourceObjects);
33 | }
34 | return pickPropertiesAsGetters(targetObject, {}, targetObject);
35 | }
36 |
37 | /**
38 | * Pick given properties from a `targetObject` and replace
39 | * them with getters that takes values from corresponding properties of `sourceObjects`.
40 | *
41 | * If one of `sourceObjects` is equal to `targetObject`,
42 | * from start the function will do this:
43 | *
44 | ```ts
45 | targetObject = JSON.parse(JSON.stringify(targetObject));
46 | ```
47 | */
48 | export function pickPropertiesAsGetters>(
49 | targetObject: T,
50 | properties: { includeProperties?: K[]; excludeProperties?: K[] },
51 | ...sourceObjects: ObjectAny[]
52 | ) {
53 | properties = properties || {};
54 | const incl = properties.includeProperties;
55 | const excl = properties.excludeProperties;
56 |
57 | for (const sourceObj of sourceObjects) {
58 | if (targetObject === sourceObj) {
59 | targetObject = JSON.parse(JSON.stringify(targetObject));
60 | break;
61 | }
62 | }
63 |
64 | sourceObjects.forEach(sourceObj => {
65 | sourceObj = sourceObj || {};
66 | Object.keys(targetObject)
67 | .filter(callback as any)
68 | .forEach(prop => {
69 | if (sourceObj.hasOwnProperty(prop)) {
70 | Object.defineProperty(targetObject, prop, {
71 | get() {
72 | return sourceObj[prop];
73 | },
74 | });
75 | }
76 | });
77 | });
78 |
79 | return targetObject;
80 |
81 | function callback(property: K) {
82 | if (incl && excl) {
83 | return incl.includes(property) && !excl.includes(property);
84 | } else if (incl) {
85 | return incl.includes(property);
86 | } else if (excl) {
87 | return !excl.includes(property);
88 | } else {
89 | return true;
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/form-array.spec.ts:
--------------------------------------------------------------------------------
1 | import { Control, ExtractModelValue } from './types';
2 | import { FormArray } from './form-array';
3 | import { FormControl } from './form-control';
4 | import { FormGroup } from './form-group';
5 |
6 | describe('FormArray', () => {
7 | class Profile {
8 | firstName: string;
9 | lastName: string;
10 | addresses: Address[];
11 | }
12 |
13 | class Address {
14 | street: string;
15 | city: string;
16 | state: string;
17 | zip: string;
18 | }
19 |
20 | xdescribe('checking types only', () => {
21 | // Waiting for resolving https://github.com/Microsoft/TypeScript/issues/30207
22 |
23 | describe('constructor', () => {
24 | it('FormArray -> FormControl', () => {
25 | const formArray = new FormArray([new FormControl('one')]);
26 | const arr: string[] = formArray.value;
27 | formArray.reset(['one', 'two']);
28 | formArray.setValue(['one', 'two']);
29 | formArray.patchValue(['one', 'two']);
30 | });
31 |
32 | it(`FormArray -> FormArray -> FormControl -> string`, () => {
33 | const formArray = new FormArray([new FormArray([new FormControl('one')])]);
34 | const val1: string[][] = formArray.value;
35 | formArray.reset([['one', 'two']]);
36 | formArray.setValue([['one', 'two']]);
37 | formArray.patchValue([['one', 'two']]);
38 | });
39 |
40 | it(`FormArray -> FormControl -> string[]`, () => {
41 | const formArray = new FormArray>([new FormControl(['one', 'two'])]);
42 | const val1: string[][] = formArray.value;
43 | formArray.reset([['one', 'two']]);
44 | formArray.setValue([['one', 'two']]);
45 | formArray.patchValue([['one', 'two']]);
46 | });
47 |
48 | it(`'one' property as an array of FormArrays`, () => {
49 | class FormModel {
50 | one: string[];
51 | }
52 | const formArray = new FormArray([
53 | new FormGroup({ one: new FormArray([new FormControl('one'), new FormControl('two')]) }),
54 | ]);
55 | const arr1: FormModel[] = formArray.value;
56 | const arr2: string[] = formArray.value[0].one;
57 | formArray.reset([{ one: ['1'] }]);
58 | formArray.setValue([{ one: ['1'] }]);
59 | formArray.patchValue([{ one: ['1'] }]);
60 | });
61 |
62 | it(`'one' property as an array of FormControls`, () => {
63 | class FormModel {
64 | one: Control;
65 | }
66 | const formArray = new FormArray([
67 | new FormGroup({ one: new FormControl(['one', 'two']) }),
68 | ]);
69 | const val1: ExtractModelValue[] = formArray.value;
70 | formArray.reset([{ one: ['1'] }]);
71 | formArray.setValue([{ one: ['1'] }]);
72 | formArray.patchValue([{ one: ['1'] }]);
73 | const val2: string[] = formArray.value[0].one;
74 | });
75 | });
76 | });
77 |
78 | describe(`checking runtime work`, () => {
79 | it('case 1', () => {
80 | const formArray = new FormArray([new FormControl()]);
81 | formArray.reset(['one']);
82 | expect(formArray.value).toEqual(['one']);
83 |
84 | formArray.reset([{ value: 'two', disabled: false }]);
85 | expect(formArray.value).toEqual(['two']);
86 | });
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/projects/contenteditable/README.md:
--------------------------------------------------------------------------------
1 | ## What is this library?
2 |
3 | This is micro Angular v4+ contenteditable directive for integration with Angular forms.
4 | It just implements [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor) for this purpose.
5 |
6 | ## Install
7 |
8 | ```bash
9 | npm install @ng-stack/contenteditable --save
10 | ```
11 |
12 | ## Usage
13 |
14 | Import and add `NgsContenteditableModule` to your project:
15 |
16 | ```ts
17 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
18 | import { NgsContenteditableModule } from '@ng-stack/contenteditable';
19 |
20 | // ...
21 |
22 | @NgModule({
23 | // ...
24 | imports: [
25 | // Import this module to get available work angular with `contenteditable`
26 | NgsContenteditableModule,
27 | // Import one or both of this modules
28 | FormsModule,
29 | ReactiveFormsModule
30 | ]
31 |
32 | // ...
33 |
34 | })
35 | ```
36 |
37 | And then you can to use it in [template-driven forms](https://angular.io/guide/forms)
38 | or [reactive forms](https://angular.io/guide/reactive-forms) like this:
39 |
40 | ```ts
41 | // In your component
42 | import { Component, OnInit } from '@angular/core';
43 | import { FormControl } from '@angular/forms';
44 |
45 | export class MyComponent implements OnInit {
46 | templateDrivenForm = 'This is contenteditable text for template-driven form';
47 | myControl = new FormControl();
48 |
49 | ngOnInit() {
50 | this.myControl.setValue(`This is contenteditable text for reactive form`);
51 | }
52 | }
53 | ```
54 |
55 | ```html
56 |
63 |
64 |
65 | {{ testForm.value | json }}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | {{ myControl.value | json }}
74 |
75 | ```
76 |
77 | ## Options
78 |
79 | ### propValueAccessor
80 |
81 | With `editable` directive you can pass optional `@Input` value for `propValueAccessor`:
82 |
83 | ```html
84 |
89 | ```
90 |
91 | Internally, `ContenteditableDirective` uses this value as follows:
92 |
93 | ```ts
94 | this.elementRef.nativeElement[this.propValueAccessor]
95 | ```
96 |
97 | By default it using `textContent`.
98 |
99 | ### `editable` as @Input property
100 |
101 | Since version 2.0.0, `@ng-stack/contenteditable` accepts `editable` as @Input property (note the square brackets):
102 |
103 | ```html
104 |
105 | ```
106 |
107 | where `isContenteditable` is a boolean variable.
108 |
109 | ### unformattedPaste
110 |
111 | Since version 1.1.0, `@ng-stack/contenteditable` takes into account experimental `unformattedPaste` attribute:
112 |
113 | ```html
114 |
119 | ```
120 |
121 | This allow copy formated text (from anywhere) and paste unformated text into HTML element with `contenteditable` attribute.
122 |
123 | `unformattedPaste` attribute is experimental because here is used obsolete [document.execCommand()](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) method to write unformated text. So far no good alternative for this method has been found.
124 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/input-file.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ElementRef,
4 | Renderer2,
5 | HostListener,
6 | forwardRef,
7 | Input,
8 | Output,
9 | EventEmitter,
10 | HostBinding,
11 | } from '@angular/core';
12 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
13 |
14 | @Directive({
15 | selector: `
16 | input[type=file][ngModel],
17 | input[type=file][formControl],
18 | input[type=file][formControlName]`,
19 | providers: [
20 | {
21 | provide: NG_VALUE_ACCESSOR,
22 | useExisting: forwardRef(() => InputFileDirective),
23 | multi: true,
24 | },
25 | ],
26 | })
27 | export class InputFileDirective implements ControlValueAccessor {
28 | private _multiple: boolean | string | undefined;
29 |
30 | @HostBinding('attr.multiple')
31 | @Input()
32 | get multiple(): boolean | string | undefined {
33 | if (
34 | this._multiple !== undefined &&
35 | this._multiple !== false &&
36 | this._multiple !== 'false'
37 | ) {
38 | return '';
39 | } else {
40 | return undefined;
41 | }
42 | }
43 |
44 | set multiple(value: boolean | string | undefined) {
45 | this._multiple = value;
46 | }
47 | @HostBinding('attr.preserveValue') @Input() preserveValue: boolean | string;
48 | @Output() select = new EventEmitter();
49 | private onChange = (value: FormData) => {};
50 | private onTouched = () => {};
51 |
52 | constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
53 |
54 | /**
55 | * Callback function that should be called when
56 | * the control's value changes in the UI.
57 | */
58 | @HostListener('change', ['$event'])
59 | callOnChange(event: any) {
60 | this.onTouched();
61 | const files = Array.from(this.elementRef.nativeElement.files);
62 | const formData = new FormData();
63 |
64 | let formInputName = this.elementRef.nativeElement.name || 'uploadFile';
65 | if (
66 | this.multiple !== undefined &&
67 | this.multiple !== false &&
68 | this.multiple !== 'false'
69 | ) {
70 | formInputName += '[]';
71 | }
72 | files.forEach((file) => formData.append(formInputName, file));
73 |
74 | this.onChange(formData);
75 | this.select.next(files);
76 | if (
77 | this.preserveValue === undefined ||
78 | this.preserveValue === false ||
79 | this.preserveValue === 'false'
80 | ) {
81 | event.target.value = null;
82 | }
83 | }
84 |
85 | /**
86 | * Writes a new value to the element.
87 | * This method will be called by the forms API to write
88 | * to the view when programmatic (model -> view) changes are requested.
89 | *
90 | * See: [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor#members)
91 | */
92 | writeValue(fileList: FileList): void {
93 | if (fileList && !(fileList instanceof FileList)) {
94 | throw new TypeError(
95 | 'Value for input[type=file] must be an instance of FileList'
96 | );
97 | }
98 | this.renderer.setProperty(this.elementRef.nativeElement, 'files', fileList);
99 | }
100 |
101 | /**
102 | * Registers a callback function that should be called when
103 | * the control's value changes in the UI.
104 | *
105 | * This is called by the forms API on initialization so it can update
106 | * the form model when values propagate from the view (view -> model).
107 | */
108 | registerOnChange(fn: () => void): void {
109 | this.onChange = fn;
110 | }
111 |
112 | /**
113 | * Registers a callback function that should be called when the control receives a change event.
114 | * This is called by the forms API on initialization so it can update the form model on change.
115 | */
116 | registerOnTouched(fn: () => void): void {
117 | this.onTouched = fn;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/assert.spec.ts:
--------------------------------------------------------------------------------
1 | import { Fn, isString, isNumber, isBoolean, isSymbol, isFunction, isObject, isArray } from './assert';
2 |
3 | // Uncomment some row to see that tsc checking types correctly.
4 |
5 | describe('assertions', () => {
6 | let value: any;
7 |
8 | it('should not to throw when assert string', () => {
9 | expect(callback).not.toThrow();
10 |
11 | function callback() {
12 | value = '';
13 | isString(value as string);
14 | // isString(value as any);
15 | // isString(null);
16 | // isString(undefined);
17 | // isString(value as number);
18 | // isString(value as boolean);
19 | // isString(value as symbol);
20 | // isString(value as object);
21 | // isString(value as Fn);
22 | }
23 | });
24 |
25 | it('should not to throw when assert number', () => {
26 | expect(callback).not.toThrow();
27 |
28 | function callback() {
29 | value = 1;
30 | isNumber(value as number);
31 | // isNumber(value as any);
32 | // isNumber(null);
33 | // isNumber(undefined);
34 | // isNumber(value as string);
35 | // isNumber(value as boolean);
36 | // isNumber(value as symbol);
37 | // isNumber(value as object);
38 | // isNumber(value as Fn);
39 | }
40 | });
41 |
42 | it('should not to throw when assert boolean', () => {
43 | expect(callback).not.toThrow();
44 |
45 | function callback() {
46 | value = true;
47 | isBoolean(value as boolean);
48 | // isBoolean(value as any);
49 | // isBoolean(null);
50 | // isBoolean(undefined);
51 | // isBoolean(value as string);
52 | // isBoolean(value as number);
53 | // isBoolean(value as symbol);
54 | // isBoolean(value as object);
55 | // isBoolean(value as Fn);
56 | }
57 | });
58 |
59 | it('should not to throw when assert symbol', () => {
60 | expect(callback).not.toThrow();
61 |
62 | function callback() {
63 | value = Symbol();
64 | isSymbol(value as symbol);
65 | // isSymbol(value as any);
66 | // isSymbol(null);
67 | // isSymbol(undefined);
68 | // isSymbol(value as string);
69 | // isSymbol(value as number);
70 | // isSymbol(value as boolean);
71 | // isSymbol(value as object);
72 | // isSymbol(value as Fn);
73 | }
74 | });
75 |
76 | it('should not to throw when assert Function', () => {
77 | expect(callback).not.toThrow();
78 |
79 | function callback() {
80 | value = () => {};
81 | isFunction(value as Fn);
82 | // isFunction(value as any);
83 | // isFunction(null);
84 | // isFunction(undefined);
85 | // isFunction(value as string);
86 | // isFunction(value as number);
87 | // isFunction(value as boolean);
88 | // isFunction(value as symbol);
89 | // isFunction(value as object);
90 | }
91 | });
92 |
93 | it('should not to throw when assert array', () => {
94 | expect(callback).not.toThrow();
95 |
96 | function callback() {
97 | value = [];
98 | isArray(value as any[]);
99 | // isArray(value as object);
100 | // isObject(value as Fn);
101 | // isObject(value as any);
102 | // isObject(null);
103 | // isObject(undefined);
104 | // isObject(value as string);
105 | // isObject(value as number);
106 | // isObject(value as boolean);
107 | // isObject(value as symbol);
108 | }
109 | });
110 |
111 | it('should not to throw when assert object', () => {
112 | expect(callback).not.toThrow();
113 |
114 | function callback() {
115 | value = {};
116 | isObject(value as object);
117 | // isObject(value as any[]);
118 | // isObject(value as Fn);
119 | // isObject(value as any);
120 | // isObject(null);
121 | // isObject(undefined);
122 | // isObject(value as string);
123 | // isObject(value as number);
124 | // isObject(value as boolean);
125 | // isObject(value as symbol);
126 | }
127 | });
128 | });
129 |
--------------------------------------------------------------------------------
/projects/contenteditable/src/lib/editable.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ElementRef,
4 | Renderer2,
5 | HostListener,
6 | HostBinding,
7 | forwardRef,
8 | Input,
9 | Inject,
10 | Attribute,
11 | } from '@angular/core';
12 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
13 | import { DOCUMENT } from '@angular/common';
14 |
15 | @Directive({
16 | selector:
17 | '[editable][formControlName],[editable][formControl],[editable][ngModel]',
18 | providers: [
19 | {
20 | provide: NG_VALUE_ACCESSOR,
21 | useExisting: forwardRef(() => EditableDirective),
22 | multi: true,
23 | },
24 | ],
25 | })
26 | export class EditableDirective implements ControlValueAccessor {
27 | @Input() propValueAccessor = 'textContent';
28 | @HostBinding('attr.contenteditable') @Input() editable: boolean | string =
29 | true;
30 |
31 | private onChange: (value: string) => void;
32 | private onTouched: () => void;
33 | private removeDisabledState: () => void;
34 |
35 | constructor(
36 | private elementRef: ElementRef,
37 | private renderer: Renderer2,
38 | @Attribute('unformattedPaste') private unformattedPaste: string,
39 | @Inject(DOCUMENT) private document: Document
40 | ) {}
41 |
42 | @HostListener('input')
43 | callOnChange() {
44 | if (typeof this.onChange == 'function') {
45 | this.onChange(this.elementRef.nativeElement[this.propValueAccessor]);
46 | }
47 | }
48 |
49 | @HostListener('blur')
50 | callOnTouched() {
51 | if (typeof this.onTouched == 'function') {
52 | this.onTouched();
53 | }
54 | }
55 |
56 | /**
57 | * Writes a new value to the element.
58 | * This method will be called by the forms API to write
59 | * to the view when programmatic (model -> view) changes are requested.
60 | *
61 | * See: [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor#members)
62 | */
63 | writeValue(value: any): void {
64 | const normalizedValue = value == null ? '' : value;
65 | this.renderer.setProperty(
66 | this.elementRef.nativeElement,
67 | this.propValueAccessor,
68 | normalizedValue
69 | );
70 | }
71 |
72 | /**
73 | * Registers a callback function that should be called when
74 | * the control's value changes in the UI.
75 | *
76 | * This is called by the forms API on initialization so it can update
77 | * the form model when values propagate from the view (view -> model).
78 | */
79 | registerOnChange(fn: () => void): void {
80 | this.onChange = fn;
81 | }
82 |
83 | /**
84 | * Registers a callback function that should be called when the control receives a blur event.
85 | * This is called by the forms API on initialization so it can update the form model on blur.
86 | */
87 | registerOnTouched(fn: () => void): void {
88 | this.onTouched = fn;
89 | }
90 |
91 | /**
92 | * This function is called by the forms API when the control status changes to or from "DISABLED".
93 | * Depending on the value, it should enable or disable the appropriate DOM element.
94 | */
95 | setDisabledState(isDisabled: boolean): void {
96 | if (isDisabled) {
97 | this.renderer.setAttribute(
98 | this.elementRef.nativeElement,
99 | 'disabled',
100 | 'true'
101 | );
102 | this.removeDisabledState = this.renderer.listen(
103 | this.elementRef.nativeElement,
104 | 'keydown',
105 | this.listenerDisabledState
106 | );
107 | } else {
108 | if (this.removeDisabledState) {
109 | this.renderer.removeAttribute(
110 | this.elementRef.nativeElement,
111 | 'disabled'
112 | );
113 | this.removeDisabledState();
114 | }
115 | }
116 | }
117 |
118 | @HostListener('paste', ['$event'])
119 | preventFormatedPaste(event: ClipboardEvent) {
120 | if (
121 | this.unformattedPaste === null ||
122 | this.unformattedPaste == 'false' ||
123 | !this.document.execCommand
124 | ) {
125 | return;
126 | }
127 | event.preventDefault();
128 | const { clipboardData } = event;
129 | const text =
130 | clipboardData?.getData('text/plain') || clipboardData?.getData('text');
131 | this.document.execCommand('insertText', false, text);
132 | }
133 |
134 | private listenerDisabledState(e: KeyboardEvent) {
135 | e.preventDefault();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/form-builder.spec.ts:
--------------------------------------------------------------------------------
1 | import { FormBuilder } from './form-builder';
2 | import { FormControl } from './form-control';
3 | import { Validators } from './validators';
4 | import { AbstractControl } from '@angular/forms';
5 | import { FormGroup } from './form-group';
6 |
7 | describe('FormBuilder', () => {
8 | xdescribe('checking types only', () => {
9 | class Address {
10 | street?: string;
11 | city?: string;
12 | state?: string;
13 | zip?: string;
14 | }
15 |
16 | class SomeArray {
17 | item1?: string;
18 | item2?: number;
19 | }
20 |
21 | class UserForm {
22 | userName: string;
23 | userEmail: string;
24 | password?: string;
25 | addresses: Address;
26 | someArray: SomeArray[];
27 | otherArray: (string | number)[];
28 | }
29 |
30 | class FormProps {
31 | userEmail: string;
32 | token: string;
33 | iAgree: boolean;
34 | }
35 |
36 | const fb = new FormBuilder();
37 |
38 | it('common', () => {
39 | const formGroup1 = fb.group({
40 | userName: 'SomeOne',
41 | // userName: 123,
42 | // userEmail: new FormGroup({}),
43 | userEmail: new FormControl('some-one@gmail.com'),
44 | addresses: fb.group({ city: 'Kyiv' }),
45 | someArray: fb.array([
46 | fb.group({ item1: 'value1' }),
47 | fb.group({ item1: 'value2' }),
48 | fb.group({ item1: 'value3' }),
49 | fb.group({ item1: 'value4' }),
50 | ]),
51 | // otherArray: fb.array([new FormControl(5), new FormGroup({}), 'three']),
52 |
53 | // otherArray: fb.array([new FormControl('one'), 2, 'three']),
54 | // Error --> Why? See https://github.com/Microsoft/TypeScript/issues/30207
55 |
56 | otherArray: fb.array([new FormControl('one'), ['two', Validators.required], 'three']),
57 | });
58 |
59 | const addresses: Address = formGroup1.value.addresses;
60 |
61 | const formGroup2 = fb.group({
62 | userEmail: [null, (control: AbstractControl) => ({ required: true, other: 1 })],
63 | token: [null, [Validators.required]],
64 | iAgree: [null, Validators.required],
65 | });
66 |
67 | formGroup1.get('otherArray')!.setValue(['string value', 2, 'three']);
68 |
69 | fb.array([
70 | fb.group({ item1: 'value1' }),
71 | fb.group({ item1: 'value2' }),
72 | fb.group({ item1: 'value3' }),
73 | fb.group({ item1: 'value4' }),
74 | ]);
75 | });
76 |
77 | it('nesting validation model', () => {
78 | interface FormGroupModel {
79 | control: string;
80 | }
81 |
82 | interface ValidModel1 {
83 | wrongPassword?: { returnedValue: boolean };
84 | wrongEmail?: { returnedValue: boolean };
85 | }
86 |
87 | interface ValidModel2 {
88 | wrongPassword?: { returnedValue: boolean };
89 | otherKey?: { returnedValue: boolean };
90 | }
91 |
92 | const form = fb.group({
93 | control: new FormControl('some value'),
94 | });
95 |
96 | const formError = form.getError('wrongEmail');
97 | const controlError = form.get('control')!.getError('email'); // Without errror, but it's wrong.
98 | });
99 | });
100 |
101 | fdescribe(`checking runtime work`, () => {
102 | describe(`control()`, () => {
103 | it('case 1', () => {
104 | const fb = new FormBuilder();
105 | expect(fb.control('one').value).toBe('one');
106 | expect(fb.control({ value: 'two', disabled: false }).value).toBe('two');
107 | });
108 | });
109 |
110 | describe(`array()`, () => {
111 | it('case 1', () => {
112 | const fb = new FormBuilder();
113 | let control = new FormControl('one');
114 | expect(fb.array([control]).value).toEqual(['one']);
115 | control = new FormControl({ value: 'two', disabled: false });
116 | expect(fb.array([control]).value).toEqual(['two']);
117 | });
118 |
119 | it('case 2', () => {
120 | const fb = new FormBuilder();
121 | const control = new FormControl('one');
122 | const group = new FormGroup({ one: control });
123 | expect(fb.array([group]).value).toEqual([{ one: 'one' }]);
124 | });
125 | });
126 | });
127 | });
128 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/form-builder.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { UntypedFormBuilder as NativeFormBuilder } from '@angular/forms';
3 |
4 | import {
5 | FbControlConfig,
6 | AbstractControlOptions,
7 | ValidatorFn,
8 | AsyncValidatorFn,
9 | ValidatorsModel,
10 | FormControlState,
11 | } from './types';
12 | import { FormGroup } from './form-group';
13 | import { FormControl } from './form-control';
14 | import { FormArray } from './form-array';
15 |
16 | @Injectable()
17 | export class FormBuilder extends NativeFormBuilder {
18 | /**
19 | * Construct a new `FormGroup` instance.
20 | *
21 | * @param controlsConfig A collection of child controls. The key for each child is the name
22 | * under which it is registered.
23 | *
24 | * @param options Configuration options object for the `FormGroup`. The object can
25 | * have two shapes:
26 | *
27 | * 1) `AbstractControlOptions` object (preferred), which consists of:
28 | * - `validators`: A synchronous validator function, or an array of validator functions
29 | * - `asyncValidators`: A single async validator or array of async validator functions
30 | * - `updateOn`: The event upon which the control should be updated (options: 'change' | 'blur' |
31 | * submit')
32 | *
33 | * 2) Legacy configuration object, which consists of:
34 | * - `validator`: A synchronous validator function, or an array of validator functions
35 | * - `asyncValidator`: A single async validator or array of async validator functions
36 | */
37 | override group(
38 | controlsConfig: { [P in keyof T]: FbControlConfig },
39 | options: AbstractControlOptions | null = null
40 | ): FormGroup {
41 | return super.group(controlsConfig, options) as FormGroup;
42 | }
43 |
44 | /**
45 | * @description
46 | * Construct a new `FormControl` with the given state, validators and options.
47 | *
48 | * @param formState Initializes the control with an initial state value, or
49 | * with an object that contains both a value and a disabled status.
50 | *
51 | * @param validatorOrOpts A synchronous validator function, or an array of
52 | * such functions, or an `AbstractControlOptions` object that contains
53 | * validation functions and a validation trigger.
54 | *
55 | * @param asyncValidator A single async validator or array of async validator
56 | * functions.
57 | *
58 | * ### Initialize a control as disabled
59 | *
60 | * The following example returns a control with an initial value in a disabled state.
61 | ```ts
62 | import {Component, Inject} from '@angular/core';
63 | import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
64 | // ...
65 | @Component({
66 | selector: 'app-disabled-form-control',
67 | template: `
68 |
69 | `
70 | })
71 | export class DisabledFormControlComponent {
72 | control: FormControl;
73 |
74 | constructor(private fb: FormBuilder) {
75 | this.control = fb.control({value: 'my val', disabled: true});
76 | }
77 | }
78 | ```
79 | */
80 | override control(
81 | formState: FormControlState = null,
82 | validatorOrOpts?:
83 | | ValidatorFn
84 | | ValidatorFn[]
85 | | AbstractControlOptions
86 | | null,
87 | asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null
88 | ): FormControl {
89 | return super.control(
90 | formState,
91 | validatorOrOpts,
92 | asyncValidator
93 | ) as FormControl;
94 | }
95 |
96 | /**
97 | * Constructs a new `FormArray` from the given array of configurations,
98 | * validators and options.
99 | *
100 | * @param controlsConfig An array of child controls or control configs. Each
101 | * child control is given an index when it is registered.
102 | *
103 | * @param validatorOrOpts A synchronous validator function, or an array of
104 | * such functions, or an `AbstractControlOptions` object that contains
105 | * validation functions and a validation trigger.
106 | *
107 | * @param asyncValidator A single async validator or array of async validator
108 | * functions.
109 | */
110 | override array- (
111 | controlsConfig: FbControlConfig
- [],
112 | validatorOrOpts?:
113 | | ValidatorFn
114 | | ValidatorFn[]
115 | | AbstractControlOptions
116 | | null,
117 | asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null
118 | ): FormArray
- {
119 | return super.array(
120 | controlsConfig,
121 | validatorOrOpts,
122 | asyncValidator
123 | ) as FormArray
- ;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/projects/api-mock/src/lib/pick-properties.spec.ts:
--------------------------------------------------------------------------------
1 | import { pickAllPropertiesAsGetters, pickPropertiesAsGetters } from './pick-properties';
2 |
3 | describe('pickAllPropertiesAsGetters', () => {
4 | it('signature1: result = pickAllPropertiesAsGetters(targetObj, sourceObj)', () => {
5 | const targetObj: any = { one: null };
6 | const sourceObj = { one: 1, two: 2 };
7 | const result = pickAllPropertiesAsGetters(targetObj, sourceObj);
8 | expect(targetObj).toBe(result);
9 | expect(sourceObj).toEqual({ one: 1, two: 2 });
10 | expect(targetObj.one).toBe(1);
11 | sourceObj.one = 11;
12 | expect(targetObj.one).toBe(11);
13 | expect(targetObj).toBe(result);
14 | });
15 |
16 | it('signature2: targetObj = pickAllPropertiesAsGetters(sourceObj)', () => {
17 | const sourceObj: any = { one: null };
18 | const targetObj = pickAllPropertiesAsGetters(sourceObj);
19 | expect(sourceObj).not.toBe(targetObj);
20 | expect(sourceObj.one).toBe(null);
21 | expect(targetObj.one).toBe(null);
22 | sourceObj.one = 11;
23 | expect(targetObj.one).toBe(11);
24 | });
25 |
26 | it('signature3: result = pickAllPropertiesAsGetters(targetObj, sourceObj1, sourceObj2)', () => {
27 | const targetObj: any = { one: null, two: null, other: null };
28 | const sourceObj1 = { one: 1, two: 2, other: 'other value' };
29 | const sourceObj2 = { one: 11, two: 22 };
30 | const result = pickAllPropertiesAsGetters(targetObj, sourceObj1, sourceObj2);
31 | expect(targetObj).toBe(result);
32 | expect(sourceObj1).toEqual({ one: 1, two: 2, other: 'other value' });
33 | expect(sourceObj2).toEqual({ one: 11, two: 22 });
34 | expect(targetObj.one).toBe(11);
35 | expect(targetObj.two).toBe(22);
36 | expect(targetObj.other).toBe('other value');
37 | sourceObj1.one = 111;
38 | sourceObj1.two = 222;
39 | sourceObj1.other = 'some thing else';
40 | expect(targetObj.one).toBe(11);
41 | expect(targetObj.two).toBe(22);
42 | expect(targetObj.other).toBe('some thing else');
43 | sourceObj2.one = 1010;
44 | sourceObj2.two = 2020;
45 | expect(targetObj.one).toBe(1010);
46 | expect(targetObj.two).toBe(2020);
47 | expect(targetObj).toBe(result);
48 | });
49 | });
50 |
51 | describe('pickPropertiesAsGetters', () => {
52 | it('signature1: result = pickPropertiesAsGetters(targetObj, null, sourceObj)', () => {
53 | const targetObj: any = { one: null };
54 | const sourceObj = { one: 1, two: 2 };
55 | const result = pickPropertiesAsGetters(targetObj, {}, sourceObj);
56 | expect(targetObj).toBe(result);
57 | expect(sourceObj).toEqual({ one: 1, two: 2 });
58 | expect(targetObj.one).toBe(1);
59 | sourceObj.one = 11;
60 | expect(targetObj.one).toBe(11);
61 | expect(targetObj).toBe(result);
62 | });
63 |
64 | it(`signature2: result = pickPropertiesAsGetters(targetObj, { includeProperties:... }, sourceObj)`, () => {
65 | const targetObj: any = { one: null, two: null };
66 | const sourceObj = { one: 1, two: 2 };
67 | const result = pickPropertiesAsGetters(targetObj, { includeProperties: ['one'] }, sourceObj);
68 | expect(targetObj).toBe(result);
69 | expect(sourceObj).toEqual({ one: 1, two: 2 });
70 | expect(targetObj.one).toBe(1);
71 | expect(targetObj.two).toBe(null);
72 | sourceObj.one = 11;
73 | sourceObj.two = 22;
74 | expect(targetObj.one).toBe(11);
75 | expect(targetObj.two).toBe(null);
76 | expect(targetObj).toBe(result);
77 | });
78 |
79 | it(`signature3: result = pickPropertiesAsGetters(targetObj, { excludeProperties:... }, sourceObj)`, () => {
80 | const targetObj: any = { one: null, two: null };
81 | const sourceObj = { one: 1, two: 2 };
82 | const result = pickPropertiesAsGetters(targetObj, { excludeProperties: ['one'] }, sourceObj);
83 | expect(targetObj).toBe(result);
84 | expect(sourceObj).toEqual({ one: 1, two: 2 });
85 | expect(targetObj.one).toBe(null);
86 | expect(targetObj.two).toBe(2);
87 | sourceObj.one = 11;
88 | sourceObj.two = 22;
89 | expect(targetObj.one).toBe(null);
90 | expect(targetObj.two).toBe(22);
91 | expect(targetObj).toBe(result);
92 | });
93 |
94 | it(`signature4: result = pickPropertiesAsGetters(targetObj, { excludeProperties:..., includeProperties:... }, sourceObj)`, () => {
95 | const targetObj: any = { one: null, two: null };
96 | const sourceObj = { one: 1, two: 2 };
97 | const result = pickPropertiesAsGetters(
98 | targetObj,
99 | { excludeProperties: ['one'], includeProperties: ['one', 'two'] },
100 | sourceObj
101 | );
102 | expect(targetObj).toBe(result);
103 | expect(sourceObj).toEqual({ one: 1, two: 2 });
104 | expect(targetObj.one).toBe(null);
105 | expect(targetObj.two).toBe(2);
106 | sourceObj.one = 11;
107 | sourceObj.two = 22;
108 | expect(targetObj.one).toBe(null);
109 | expect(targetObj.two).toBe(22);
110 | expect(targetObj).toBe(result);
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/validators.spec.ts:
--------------------------------------------------------------------------------
1 | import { Validators } from './validators';
2 |
3 | describe('Validators', () => {
4 | describe('fileRequired', () => {
5 | it('null as value', () => {
6 | const validator = Validators.fileRequired;
7 | expect(validator({ value: null } as any)).toEqual({ fileRequired: true });
8 | });
9 |
10 | it('some object as value', () => {
11 | const validator = Validators.fileRequired;
12 | expect(validator({ value: { one: 1 } } as any)).toEqual({ fileRequired: true });
13 | });
14 |
15 | it('one file as value', () => {
16 | const content = '1'.repeat(10);
17 |
18 | const formData = new FormData();
19 | const blob = new Blob([content], { type: 'text/plain' });
20 | const file = new File([blob], 'any-name.jpg');
21 | formData.append('fileUpload', file);
22 | const validator = Validators.fileRequired;
23 | expect(validator({ value: formData } as any)).toBe(null);
24 | });
25 | });
26 |
27 | describe('filesMinLength', () => {
28 | it('one file when expected min one file', () => {
29 | const minLength = 1;
30 |
31 | const formData = new FormData();
32 | const file = new File([], 'any-name.jpg');
33 | formData.append('fileUpload', file);
34 | const validator = Validators.filesMinLength(minLength);
35 | expect(validator({ value: formData } as any)).toBe(null);
36 | });
37 |
38 | it('one file when expected min two files', () => {
39 | const minLength = 2;
40 | const actualLength = 1;
41 |
42 | const formData = new FormData();
43 | const file = new File([], 'any-name.jpg');
44 | formData.append('fileUpload', file);
45 | const validator = Validators.filesMinLength(minLength);
46 | const err = { filesMinLength: { requiredLength: minLength, actualLength } };
47 | expect(validator({ value: formData } as any)).toEqual(err);
48 | });
49 | });
50 |
51 | describe('filesMaxLength', () => {
52 | it('one file when expected max one file', () => {
53 | const maxLength = 1;
54 |
55 | const formData = new FormData();
56 | const file = new File([], 'any-name.jpg');
57 | formData.append('fileUpload', file);
58 | const validator = Validators.filesMaxLength(maxLength);
59 | expect(validator({ value: formData } as any)).toBe(null);
60 | });
61 |
62 | it('three files when expected max two files', () => {
63 | const maxLength = 2;
64 | const actualLength = 3;
65 |
66 | const formData = new FormData();
67 | const file = new File([], 'any-name.jpg');
68 | formData.append('fileUpload', file);
69 | formData.append('fileUpload', file);
70 | formData.append('fileUpload', file);
71 | const validator = Validators.filesMaxLength(maxLength);
72 | const err = { filesMaxLength: { requiredLength: maxLength, actualLength } };
73 | expect(validator({ value: formData } as any)).toEqual(err);
74 | });
75 | });
76 |
77 | describe('fileMaxSize', () => {
78 | it('null as content', () => {
79 | const maxSize = 10;
80 | const content = null as any;
81 |
82 | const formData = new FormData();
83 | const blob = new Blob([content], { type: 'text/plain' });
84 | const file = new File([blob], 'any-name.jpg');
85 | formData.append('fileUpload', file);
86 | const validator = Validators.fileMaxSize(maxSize);
87 | expect(validator({ value: formData } as any)).toBe(null);
88 | });
89 |
90 | it('empty content', () => {
91 | const maxSize = 10;
92 | const content = '';
93 |
94 | const formData = new FormData();
95 | const blob = new Blob([content], { type: 'text/plain' });
96 | const file = new File([blob], 'any-name.jpg');
97 | formData.append('fileUpload', file);
98 | const validator = Validators.fileMaxSize(maxSize);
99 | expect(validator({ value: formData } as any)).toBe(null);
100 | });
101 |
102 | it('acceptable content', () => {
103 | const maxSize = 10;
104 | const content = '1'.repeat(10);
105 |
106 | const formData = new FormData();
107 | const blob = new Blob([content], { type: 'text/plain' });
108 | const file = new File([blob], 'any-name.jpg');
109 | formData.append('fileUpload', file);
110 | const validator = Validators.fileMaxSize(maxSize);
111 | expect(validator({ value: formData } as any)).toBe(null);
112 | });
113 |
114 | it('exceeded max size', () => {
115 | const maxSize = 10;
116 | const actualSize = 11;
117 | const content = '1'.repeat(actualSize);
118 |
119 | const formData = new FormData();
120 | const blob = new Blob([content], { type: 'text/plain' });
121 | const file = new File([blob], 'any-name.jpg');
122 | formData.append('fileUpload', file);
123 | const validator = Validators.fileMaxSize(maxSize);
124 | const err = { fileMaxSize: { requiredSize: maxSize, actualSize, file } };
125 | expect(validator({ value: formData } as any)).toEqual(err);
126 | });
127 | });
128 | });
129 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ng-stack-13": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | },
12 | "@schematics/angular:application": {
13 | "strict": true
14 | }
15 | },
16 | "root": "",
17 | "sourceRoot": "src",
18 | "prefix": "app",
19 | "architect": {
20 | "build": {
21 | "builder": "@angular-devkit/build-angular:browser",
22 | "options": {
23 | "outputPath": "dist/ng-stack-13",
24 | "index": "src/index.html",
25 | "main": "src/main.ts",
26 | "polyfills": "src/polyfills.ts",
27 | "tsConfig": "tsconfig.app.json",
28 | "inlineStyleLanguage": "scss",
29 | "assets": [
30 | "src/favicon.ico",
31 | "src/assets"
32 | ],
33 | "styles": [
34 | "src/styles.scss"
35 | ],
36 | "scripts": []
37 | },
38 | "configurations": {
39 | "production": {
40 | "budgets": [
41 | {
42 | "type": "initial",
43 | "maximumWarning": "500kb",
44 | "maximumError": "1mb"
45 | },
46 | {
47 | "type": "anyComponentStyle",
48 | "maximumWarning": "2kb",
49 | "maximumError": "4kb"
50 | }
51 | ],
52 | "fileReplacements": [
53 | {
54 | "replace": "src/environments/environment.ts",
55 | "with": "src/environments/environment.prod.ts"
56 | }
57 | ],
58 | "outputHashing": "all"
59 | },
60 | "development": {
61 | "buildOptimizer": false,
62 | "optimization": false,
63 | "vendorChunk": true,
64 | "extractLicenses": false,
65 | "sourceMap": true,
66 | "namedChunks": true
67 | }
68 | },
69 | "defaultConfiguration": "production"
70 | },
71 | "serve": {
72 | "builder": "@angular-devkit/build-angular:dev-server",
73 | "configurations": {
74 | "production": {
75 | "browserTarget": "ng-stack-13:build:production"
76 | },
77 | "development": {
78 | "browserTarget": "ng-stack-13:build:development"
79 | }
80 | },
81 | "defaultConfiguration": "development"
82 | },
83 | "extract-i18n": {
84 | "builder": "@angular-devkit/build-angular:extract-i18n",
85 | "options": {
86 | "browserTarget": "ng-stack-13:build"
87 | }
88 | },
89 | "test": {
90 | "builder": "@angular-devkit/build-angular:karma",
91 | "options": {
92 | "main": "src/test.ts",
93 | "polyfills": "src/polyfills.ts",
94 | "tsConfig": "tsconfig.spec.json",
95 | "karmaConfig": "karma.conf.js",
96 | "inlineStyleLanguage": "scss",
97 | "assets": [
98 | "src/favicon.ico",
99 | "src/assets"
100 | ],
101 | "styles": [
102 | "src/styles.scss"
103 | ],
104 | "scripts": []
105 | }
106 | }
107 | }
108 | },
109 | "forms": {
110 | "projectType": "library",
111 | "root": "projects/forms",
112 | "sourceRoot": "projects/forms/src",
113 | "prefix": "lib",
114 | "architect": {
115 | "build": {
116 | "builder": "@angular-devkit/build-angular:ng-packagr",
117 | "options": {
118 | "project": "projects/forms/ng-package.json"
119 | },
120 | "configurations": {
121 | "production": {
122 | "tsConfig": "projects/forms/tsconfig.lib.prod.json"
123 | },
124 | "development": {
125 | "tsConfig": "projects/forms/tsconfig.lib.json"
126 | }
127 | },
128 | "defaultConfiguration": "production"
129 | },
130 | "test": {
131 | "builder": "@angular-devkit/build-angular:karma",
132 | "options": {
133 | "main": "projects/forms/src/test.ts",
134 | "tsConfig": "projects/forms/tsconfig.spec.json",
135 | "karmaConfig": "projects/forms/karma.conf.js"
136 | }
137 | }
138 | }
139 | },
140 | "contenteditable": {
141 | "projectType": "library",
142 | "root": "projects/contenteditable",
143 | "sourceRoot": "projects/contenteditable/src",
144 | "prefix": "lib",
145 | "architect": {
146 | "build": {
147 | "builder": "@angular-devkit/build-angular:ng-packagr",
148 | "options": {
149 | "project": "projects/contenteditable/ng-package.json"
150 | },
151 | "configurations": {
152 | "production": {
153 | "tsConfig": "projects/contenteditable/tsconfig.lib.prod.json"
154 | },
155 | "development": {
156 | "tsConfig": "projects/contenteditable/tsconfig.lib.json"
157 | }
158 | },
159 | "defaultConfiguration": "production"
160 | },
161 | "test": {
162 | "builder": "@angular-devkit/build-angular:karma",
163 | "options": {
164 | "main": "projects/contenteditable/src/test.ts",
165 | "tsConfig": "projects/contenteditable/tsconfig.spec.json",
166 | "karmaConfig": "projects/contenteditable/karma.conf.js"
167 | }
168 | }
169 | }
170 | },
171 | "api-mock": {
172 | "projectType": "library",
173 | "root": "projects/api-mock",
174 | "sourceRoot": "projects/api-mock/src",
175 | "prefix": "lib",
176 | "architect": {
177 | "build": {
178 | "builder": "@angular-devkit/build-angular:ng-packagr",
179 | "options": {
180 | "project": "projects/api-mock/ng-package.json"
181 | },
182 | "configurations": {
183 | "production": {
184 | "tsConfig": "projects/api-mock/tsconfig.lib.prod.json"
185 | },
186 | "development": {
187 | "tsConfig": "projects/api-mock/tsconfig.lib.json"
188 | }
189 | },
190 | "defaultConfiguration": "production"
191 | },
192 | "test": {
193 | "builder": "@angular-devkit/build-angular:karma",
194 | "options": {
195 | "main": "projects/api-mock/src/test.ts",
196 | "tsConfig": "projects/api-mock/tsconfig.spec.json",
197 | "karmaConfig": "projects/api-mock/karma.conf.js"
198 | }
199 | }
200 | }
201 | }
202 | },
203 | "defaultProject": "ng-stack-13"
204 | }
205 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/form-control.ts:
--------------------------------------------------------------------------------
1 | import { UntypedFormControl as NativeFormControl } from '@angular/forms';
2 |
3 | import { Observable } from 'rxjs';
4 |
5 | import {
6 | Status,
7 | ValidationErrors,
8 | StringKeys,
9 | ValidatorFn,
10 | AsyncValidatorFn,
11 | AbstractControlOptions,
12 | ValidatorsModel,
13 | ExtractControlValue,
14 | FormControlState,
15 | } from './types';
16 |
17 | export class FormControl extends NativeFormControl {
18 | override readonly value: ExtractControlValue;
19 | override readonly valueChanges: Observable>;
20 | override readonly status: Status;
21 | override readonly statusChanges: Observable;
22 | override readonly errors: ValidationErrors | null;
23 |
24 | /**
25 | * Creates a new `FormControl` instance.
26 | *
27 | * @param formState Initializes the control with an initial value,
28 | * or an object that defines the initial value and disabled state.
29 | *
30 | * @param validatorOrOpts A synchronous validator function, or an array of
31 | * such functions, or an `AbstractControlOptions` object that contains validation functions
32 | * and a validation trigger.
33 | *
34 | * @param asyncValidator A single async validator or array of async validator functions
35 | *
36 | */
37 | constructor(
38 | formState: FormControlState = null,
39 | validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null,
40 | asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null
41 | ) {
42 | super(formState, validatorOrOpts, asyncValidator);
43 | }
44 |
45 | /**
46 | * Sets a new value for the form control.
47 | *
48 | * @param value The new value for the control.
49 | * @param options Configuration options that determine how the control proopagates changes
50 | * and emits events when the value changes.
51 | * The configuration options are passed to the
52 | * [updateValueAndValidity](https://angular.io/api/forms/AbstractControl#updateValueAndValidity) method.
53 | *
54 | * * `onlySelf`: When true, each change only affects this control, and not its parent. Default is
55 | * false.
56 | * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
57 | * `valueChanges`
58 | * observables emit events with the latest status and value when the control value is updated.
59 | * When false, no events are emitted.
60 | * * `emitModelToViewChange`: When true or not supplied (the default), each change triggers an
61 | * `onChange` event to
62 | * update the view.
63 | * * `emitViewToModelChange`: When true or not supplied (the default), each change triggers an
64 | * `ngModelChange`
65 | * event to update the model.
66 | *
67 | */
68 | override setValue(
69 | value: ExtractControlValue,
70 | options: {
71 | onlySelf?: boolean;
72 | emitEvent?: boolean;
73 | emitModelToViewChange?: boolean;
74 | emitViewToModelChange?: boolean;
75 | } = {}
76 | ) {
77 | return super.setValue(value, options);
78 | }
79 |
80 | /**
81 | * Patches the value of a control.
82 | *
83 | * This function is functionally the same as [setValue](https://angular.io/api/forms/FormControl#setValue) at this level.
84 | * It exists for symmetry with [patchValue](https://angular.io/api/forms/FormGroup#patchValue) on `FormGroups` and
85 | * `FormArrays`, where it does behave differently.
86 | *
87 | * See also: `setValue` for options
88 | */
89 | override patchValue(
90 | value: ExtractControlValue,
91 | options: {
92 | onlySelf?: boolean;
93 | emitEvent?: boolean;
94 | emitModelToViewChange?: boolean;
95 | emitViewToModelChange?: boolean;
96 | } = {}
97 | ) {
98 | return super.patchValue(value, options);
99 | }
100 |
101 | /**
102 | * Resets the form control, marking it `pristine` and `untouched`, and setting
103 | * the value to null.
104 | *
105 | * @param formState Resets the control with an initial value,
106 | * or an object that defines the initial value and disabled state.
107 | *
108 | * @param options Configuration options that determine how the control propagates changes
109 | * and emits events after the value changes.
110 | *
111 | * * `onlySelf`: When true, each change only affects this control, and not its parent. Default is
112 | * false.
113 | * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
114 | * `valueChanges`
115 | * observables emit events with the latest status and value when the control is reset.
116 | * When false, no events are emitted.
117 | *
118 | */
119 | override reset(
120 | formState: FormControlState = null,
121 | options: {
122 | onlySelf?: boolean;
123 | emitEvent?: boolean;
124 | } = {}
125 | ) {
126 | return super.reset(formState, options);
127 | }
128 |
129 | /**
130 | * In `FormControl`, this method always returns `null`.
131 | */
132 | override get(): null {
133 | return null;
134 | }
135 |
136 | /**
137 | * Sets the synchronous validators that are active on this control. Calling
138 | * this overwrites any existing sync validators.
139 | */
140 | override setValidators(newValidator: ValidatorFn | ValidatorFn[] | null) {
141 | return super.setValidators(newValidator);
142 | }
143 |
144 | /**
145 | * Sets the async validators that are active on this control. Calling this
146 | * overwrites any existing async validators.
147 | */
148 | override setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[] | null) {
149 | return super.setAsyncValidators(newValidator);
150 | }
151 |
152 | /**
153 | * Sets errors on a form control when running validations manually, rather than automatically.
154 | *
155 | * Calling `setErrors` also updates the validity of the parent control.
156 | *
157 | * ### Manually set the errors for a control
158 | *
159 | * ```ts
160 | * const login = new FormControl('someLogin');
161 | * login.setErrors({
162 | * notUnique: true
163 | * });
164 | *
165 | * expect(login.valid).toEqual(false);
166 | * expect(login.errors).toEqual({ notUnique: true });
167 | *
168 | * login.setValue('someOtherLogin');
169 | *
170 | * expect(login.valid).toEqual(true);
171 | * ```
172 | */
173 | override setErrors(errors: ValidationErrors | null, opts: { emitEvent?: boolean } = {}) {
174 | return super.setErrors(errors, opts);
175 | }
176 |
177 | /**
178 | * Reports error data for the current control.
179 | *
180 | * @param errorCode The code of the error to check.
181 | *
182 | * @returns error data for that particular error. If an error is not present,
183 | * null is returned.
184 | */
185 | override getError = any>(errorCode: K) {
186 | return super.getError(errorCode) as V[K] | null;
187 | }
188 |
189 | /**
190 | * Reports whether the current control has the error specified.
191 | *
192 | * @param errorCode The code of the error to check.
193 | *
194 | * @returns whether the given error is present in the current control.
195 | *
196 | * If an error is not present, false is returned.
197 | */
198 | override hasError = any>(errorCode: K) {
199 | return super.hasError(errorCode);
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/types.spec.ts:
--------------------------------------------------------------------------------
1 | import { FormArray } from './form-array';
2 | import { FormGroup } from './form-group';
3 | import { FormControl } from './form-control';
4 | import {
5 | ControlType,
6 | FbControlConfig,
7 | FbControl,
8 | Control,
9 | ExtractGroupValue,
10 | ExtractGroupStateValue,
11 | ExtractModelValue,
12 | ExtractControlValue,
13 | FormControlState,
14 | } from './types';
15 |
16 | xdescribe('checking types only', () => {
17 | // tslint:disable: prefer-const
18 |
19 | class Model {
20 | street: string;
21 | city: string;
22 | state: string;
23 | zip: string;
24 | }
25 |
26 | describe('Control of FormGroup', () => {
27 | describe('ControlType', () => {
28 | it('should return FormArray type', () => {
29 | let valueWithType: ControlType = {} as any;
30 | const formArray: FormArray = valueWithType;
31 | });
32 |
33 | it('should return FormControl', () => {
34 | let valueWithType: ControlType> = {} as any;
35 | const formControl: FormControl = valueWithType;
36 | });
37 |
38 | it('should return FormControl', () => {
39 | let valueWithType: ControlType> = {} as any;
40 | const formControl: FormControl = valueWithType;
41 | });
42 |
43 | it('should return FormGroup', () => {
44 | let valueWithType: ControlType = {} as any;
45 | const formGroup: FormGroup = valueWithType;
46 | });
47 |
48 | it('should return FormControl', () => {
49 | let valueWithType: ControlType = {} as any;
50 | const formControl: FormControl = valueWithType;
51 | });
52 | });
53 |
54 | describe('ExtractControlValue', () => {
55 | it('case 1', () => {
56 | let value: ExtractControlValue = '';
57 | const val: string = value;
58 | });
59 |
60 | it('case 2', () => {
61 | let value: ExtractControlValue = [''];
62 | const val: string[] = value;
63 | });
64 |
65 | it('case 3', () => {
66 | let value: ExtractControlValue = true;
67 | const val: boolean = value;
68 | });
69 |
70 | it('case 4', () => {
71 | let value: ExtractControlValue = [true];
72 | const val: boolean[] = value;
73 | });
74 |
75 | it('case 5', () => {
76 | let value: ExtractControlValue<{ one: string }> = {one: ''};
77 | const val: { one: string } = value;
78 | });
79 |
80 | it('case 6', () => {
81 | let value: ExtractControlValue> = ['one'];
82 | const val: string[] = value;
83 | });
84 |
85 | it('case 7', () => {
86 | let value: ExtractControlValue> = [true];
87 | const val: boolean[] = value;
88 | });
89 |
90 | it('case 8', () => {
91 | let value: ExtractControlValue> = { one: 'val' };
92 | const val: { one: string } = value;
93 | });
94 |
95 | it('case 9', () => {
96 | let value: ExtractControlValue<{ one: Control }> = { one: [''] } as any;
97 | const val: { one: string[] } = value;
98 | });
99 | });
100 |
101 | describe('FormControlState', () => {
102 | let value1: FormControlState;
103 | value1 = '';
104 | value1 = { value: '', disabled: false };
105 | });
106 |
107 | describe('ControlValue', () => {
108 | it('should clear value outside an object (for FormControl)', () => {
109 | interface FormModel {
110 | one: string;
111 | }
112 | let value: ExtractGroupValue> = {} as any;
113 | const obj1: FormModel = value;
114 | const str1: string = value.one;
115 | const control = new FormControl(value);
116 | const obj2: FormModel = control.value;
117 | const str2: string = control.value.one;
118 | });
119 |
120 | it('should clear value inside an object (for FormGroup)', () => {
121 | interface FormModel {
122 | one: Control;
123 | }
124 | let value: ExtractGroupValue= {} as any;
125 | const arr1: string[] = value.one;
126 | });
127 | });
128 |
129 | describe('ExtractFormValue', () => {
130 | it('case 1', () => {
131 | let value: ExtractModelValue;
132 | const val1: string = value;
133 | const val2: number = value;
134 | const val3: string[] = value;
135 | const val4: { one: 1 } = value;
136 | });
137 |
138 | it('case 2', () => {
139 | let value: ExtractModelValue = '';
140 | const val: string = value;
141 | });
142 |
143 | it('case 3', () => {
144 | let value: ExtractModelValue = ['one']
145 | const val: string[] = value;
146 | });
147 |
148 | it('case 4', () => {
149 | interface FormModel {
150 | one: string[];
151 | }
152 | let value: ExtractModelValue[]> = {} as any;
153 | const val: FormModel[] = value;
154 | });
155 |
156 | it('case 5', () => {
157 | interface FormModel {
158 | one: Control;
159 | }
160 | let value: ExtractModelValue = ['one'] as any;
161 | const val: string[] = value.one;
162 | });
163 |
164 | it('case 6', () => {
165 | interface FormModel {
166 | value: { other: string; city: string; street: string };
167 | disabled: false;
168 | }
169 | let value: ExtractModelValue = {} as any;
170 | const val: FormModel = value;
171 | });
172 | });
173 |
174 | describe('FormGroupState', () => {
175 | it('case 1', () => {
176 | let some: ExtractGroupStateValue<{ one: number }> = {} as any;;
177 | some.one = 1;
178 | some.one = { value: 1, disabled: true };
179 | });
180 | });
181 |
182 | type T1 = ControlType;
183 | type T2 = ControlType;
184 | type T3 = ControlType;
185 | type T4 = ControlType<'one' | 'two'>;
186 | type T5 = ControlType<{ one: string; two: number }>;
187 | });
188 |
189 | describe('Control of FormBuilder', () => {
190 | it('should return FormArray type', () => {
191 | let valueWithType: FbControlConfig = {} as any;
192 | const formArray: FormArray = valueWithType;
193 | });
194 |
195 | it('should return FbControl', () => {
196 | let valueWithType: FbControlConfig> = {} as any;
197 | const formControl: FbControl = valueWithType;
198 | });
199 |
200 | it('should return FormGroup', () => {
201 | let valueWithType: FbControlConfig;
202 | const formGroup: FormGroup = valueWithType = {} as any;
203 | });
204 |
205 | it('should return FbControl', () => {
206 | let valueWithType: FbControlConfig;
207 | const fbControl: FbControl = valueWithType = {} as any;
208 | });
209 |
210 | type T1 = FbControlConfig;
211 | type T2 = FbControlConfig;
212 | type T3 = FbControlConfig;
213 | type T4 = FbControlConfig<'one' | 'two'>;
214 | type T5 = FbControlConfig<{ one: string; two: number }>;
215 | });
216 | });
217 |
--------------------------------------------------------------------------------
/projects/api-mock/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Params } from '@angular/router';
3 | import { HttpHeaders } from '@angular/common/http';
4 | import { Status } from './http-status-codes';
5 | import { pickProperties } from './pick-properties';
6 |
7 | export abstract class ApiMockService {
8 | abstract getRoutes(): ApiMockRootRoute[];
9 | }
10 |
11 | /**
12 | * Interface for `HttpBackendService` configuration options.
13 | */
14 | @Injectable()
15 | export class ApiMockConfig {
16 | /**
17 | * - `true` - should pass unrecognized request URL through to original backend.
18 | * - `false` - (default) return 404 code.
19 | */
20 | passThruUnknownUrl? = false;
21 | /**
22 | * - Do you need to clear previous console logs?
23 | *
24 | * Clears logs between previous route `NavigationStart` and current `NavigationStart` events.
25 | */
26 | clearPrevLog? = false;
27 | showLog? = true;
28 | cacheFromLocalStorage? = false;
29 | /**
30 | * By default `apiMockCachedData`.
31 | */
32 | localStorageKey? = 'apiMockCachedData';
33 | /**
34 | * Simulate latency by delaying response (in milliseconds).
35 | */
36 | delay? = 500;
37 | /**
38 | * - `true` - (default) 204 code - should NOT return the item after a `POST` an item with existing ID.
39 | * - `false` - 200 code - return the item.
40 | *
41 | * Tip:
42 | * > **204 No Content**
43 | *
44 | * > The server successfully processed the request and is not returning any content.
45 | */
46 | postUpdate204? = true;
47 | /**
48 | * - `true` - 409 code - should NOT update existing item with `POST`.
49 | * - `false` - (default) 200 code - OK to update.
50 | *
51 | * Tip:
52 | * > **409 Conflict**
53 | *
54 | * > Indicates that the request could not be processed because of conflict in the current
55 | * > state of the resource, such as an edit conflict between multiple simultaneous updates.
56 | */
57 | postUpdate409? = false;
58 | /**
59 | * - `true` - (default) 204 code - should NOT return the item after a `PUT` an item with existing ID.
60 | * - `false` - 200 code - return the item.
61 | *
62 | * Tip:
63 | * > **204 No Content**
64 | *
65 | * > The server successfully processed the request and is not returning any content.
66 | */
67 | putUpdate204? = true;
68 | /**
69 | * - `true` - (default) 404 code - if `PUT` item with that ID not found.
70 | * - `false` - create new item.
71 | */
72 | putUpdate404? = true;
73 | /**
74 | * - `true` - (default) 204 code - should NOT return the item after a `PATCH` an item with existing ID.
75 | * - `false` - 200 code - return the item.
76 | *
77 | * Tip:
78 | * > **204 No Content**
79 | *
80 | * > The server successfully processed the request and is not returning any content.
81 | */
82 | patchUpdate204? = true;
83 | /**
84 | * - `true` - (default) 404 code - if item with that ID not found.
85 | * - `false` - 204 code.
86 | *
87 | * Tip:
88 | * > **204 No Content**
89 | *
90 | * > The server successfully processed the request and is not returning any content.
91 | */
92 | deleteNotFound404? = true;
93 |
94 | constructor(apiMockConfig?: ApiMockConfig) {
95 | pickProperties(this, apiMockConfig as any);
96 | }
97 | }
98 |
99 | /**
100 | * It is just `{ [key: string]: any }` an object interface.
101 | */
102 | export interface ObjectAny {
103 | [key: string]: any;
104 | }
105 |
106 | export type CallbackAny = (...params: any[]) => any;
107 |
108 | /**
109 | * For more info, see [HTTP Request methods](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods)
110 | */
111 | export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'OPTIONS' | 'CONNECT' | 'PATCH';
112 |
113 | export interface ApiMockDataCallbackOptions {
114 | items?: I;
115 | itemId?: string;
116 | httpMethod?: HttpMethod;
117 | parents?: P;
118 | queryParams?: Params;
119 | /**
120 | * Request body.
121 | */
122 | reqBody?: any;
123 | reqHeaders?: ObjectAny;
124 | }
125 |
126 | export interface ApiMockResponseCallbackOptions<
127 | I extends ObjectAny[] = ObjectAny[],
128 | P extends ObjectAny[] = ObjectAny[]
129 | > extends ApiMockDataCallbackOptions {
130 | /**
131 | * Response body.
132 | */
133 | resBody?: any;
134 | }
135 |
136 | export type ApiMockDataCallback = (
137 | opts?: ApiMockDataCallbackOptions
138 | ) => I;
139 |
140 | export type ApiMockResponseCallback = (
141 | opts?: ApiMockResponseCallbackOptions
142 | ) => any;
143 |
144 | export interface ApiMockRoute {
145 | path: string;
146 | dataCallback?: ApiMockDataCallback;
147 | /**
148 | * Properties for a list items that returns from `dataCallback()`, but
149 | * you need init this properties: `propertiesForList: { firstProp: null, secondProp: null }`.
150 | */
151 | propertiesForList?: ObjectAny;
152 | responseCallback?: ApiMockResponseCallback;
153 | /**
154 | * You can store almost all mockData in localStorage, but with exception of store
155 | * from individual routes with `ignoreDataFromLocalStorage == true`.
156 | *
157 | * By default `ignoreDataFromLocalStorage == false`.
158 | */
159 | ignoreDataFromLocalStorage?: boolean;
160 | children?: ApiMockRoute[];
161 | }
162 |
163 | export interface ApiMockRootRoute extends ApiMockRoute {
164 | host?: string;
165 | }
166 |
167 | export class CacheData {
168 | [routeKey: string]: MockData;
169 | }
170 |
171 | export type PartialRoutes = Array<{ path: string; length: number; index: number }>;
172 |
173 | export interface RouteDryMatch {
174 | splitedUrl: string[];
175 | splitedRoute: string[];
176 | routes: ApiMockRoute[];
177 | hasLastRestId: boolean;
178 | lastPrimaryKey?: string;
179 | }
180 |
181 | /**
182 | * If we have URL `api/posts/123/comments/456` with route `api/posts/:postId/comments/:commentId`,
183 | * we have two "chain params" for `api/posts` and for `api/posts/123/comments`.
184 | */
185 | export interface ChainParam {
186 | cacheKey: string;
187 | route: ApiMockRoute;
188 | primaryKey: string;
189 | restId?: string;
190 | }
191 |
192 | export interface MockData {
193 | /**
194 | * Array of full version of items from REST resource,
195 | * it is a single resource of true for given REST resource.
196 | *
197 | * - If HTTP-request have `GET` method with restId, we returns item from this array.
198 | * - If HTTP-request have `POST`, `PUT`, `PATCH` or `DELETE` method,
199 | * this actions will be doing with item from this array.
200 | */
201 | writeableData: ObjectAny[];
202 | /**
203 | * Array of composed objects with properties as getters (readonly properties).
204 | *
205 | * - If HTTP-request have `GET` method without restId, we return this array,
206 | * where items may have reduce version of REST resource.
207 | */
208 | readonlyData: ObjectAny[];
209 | }
210 |
211 | /**
212 | * Http Response Options.
213 | */
214 | export interface ResponseOptions {
215 | headers: HttpHeaders;
216 | status: number;
217 | body?: any;
218 | statusText?: string;
219 | url?: string;
220 | }
221 |
222 | export interface ResponseOptionsLog {
223 | status: Status;
224 | body: any;
225 | headers?: ObjectAny;
226 | }
227 |
228 | export function isFormData(formData: FormData): formData is FormData {
229 | return FormData !== undefined && formData instanceof FormData;
230 | }
231 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | import { AbstractControl } from '@angular/forms';
2 |
3 | import { Observable } from 'rxjs';
4 |
5 | import { FormArray } from './form-array';
6 | import { FormGroup } from './form-group';
7 | import { FormControl } from './form-control';
8 |
9 | /**
10 | * This type marks a property of a form model as property
11 | * which is intended for an instance of `FormControl`.
12 | *
13 | * If a property of your form model have a primitive type,
14 | * in appropriate form field the instance of `FormControl` will be automatically assigned.
15 | * But if the property have a type that extends `object` - you need `Control`.
16 | *
17 | * ### Example:
18 | ```ts
19 | import { FormBuilder, Control } from '@ng-stack/forms';
20 |
21 | const fb = new FormBuilder();
22 |
23 | // Form Model
24 | interface Person {
25 | id: number;
26 | name: string;
27 | birthDate: Control; // Here should be FormControl, instead of a FormGroup
28 | }
29 |
30 | const form = fb.group({
31 | id: 123,
32 | name: 'John Smith',
33 | birthDate: new Date(1977, 6, 30),
34 | });
35 |
36 | const birthDate: Date = form.value.birthDate;
37 | ```
38 | * ## External form model
39 | *
40 | * If the form model interface comes from an external library, you can do the following:
41 | *
42 | ```ts
43 | import { FormBuilder, Control } from '@ng-stack/forms';
44 |
45 | const fb = new FormBuilder();
46 |
47 | // External Form Model
48 | interface ExternalPerson {
49 | id: number;
50 | name: string;
51 | birthDate: Date;
52 | }
53 |
54 | const configForm: ExternalPerson = {
55 | id: 123,
56 | name: 'John Smith',
57 | birthDate: new Date(1977, 6, 30),
58 | };
59 |
60 | interface Person extends ExternalPerson {
61 | birthDate: Control;
62 | }
63 |
64 | const form = fb.group(configForm); // `Control` type is compatible with `Date` type.
65 |
66 | const birthDate: Date = form.value.birthDate; // `Control` type is compatible with `Date` type.
67 | ```
68 | */
69 | export type Control = T & UniqToken;
70 |
71 | const sym = Symbol();
72 |
73 | interface UniqToken {
74 | [sym]: never;
75 | }
76 |
77 | /**
78 | * Extract `keyof T` with string keys.
79 | */
80 | export type StringKeys = Extract;
81 |
82 | type ExtractAny = T extends Extract ? any : never;
83 |
84 | /**
85 | * This type is a conditional type that automatically detects
86 | * appropriate types for form controls by given type for its generic.
87 | */
88 | export type ControlType = [T] extends [ExtractAny]
89 | ? FormGroup | FormControl | FormArray
90 | : [T] extends [Control]
91 | ? FormControl
92 | : [T] extends [Array]
93 | ? FormArray
-
94 | : [T] extends [object]
95 | ? FormGroup
96 | : FormControl;
97 |
98 | export type FormControlState =
99 | | null
100 | | ExtractModelValue
101 | | {
102 | value: null | ExtractModelValue;
103 | disabled: boolean;
104 | };
105 |
106 | /**
107 | * Clears the form model from `Control` type.
108 | */
109 | export type ExtractModelValue = [T] extends [ExtractAny]
110 | ? any
111 | : [T] extends [Array]
112 | ? Array>
113 | : [T] extends [Control]
114 | ? ControlModel
115 | : [T] extends [object]
116 | ? ExtractGroupValue
117 | : T;
118 |
119 | export type ExtractControlValue = [T] extends [Control] ? ControlModel : T;
120 |
121 | /**
122 | * Clears the form model (as object) from `Control` type.
123 | */
124 | export type ExtractGroupValue = {
125 | [P in keyof T]: ExtractModelValue;
126 | };
127 |
128 | export type ExtractGroupStateValue = {
129 | [P in keyof T]: FormControlState;
130 | };
131 |
132 | /**
133 | * Form builder control config.
134 | */
135 | export type FbControlConfig = [T] extends [ExtractAny]
136 | ? FormGroup | FbControl | FormArray
137 | : [T] extends [Control]
138 | ? FbControl
139 | : [T] extends [Array]
140 | ? FormArray
-
141 | : [T] extends [object]
142 | ? FormGroup
143 | : FbControl;
144 |
145 | /**
146 | * Form builder control.
147 | */
148 | export type FbControl =
149 | | ExtractModelValue
150 | | FormControlState
151 | | [
152 | FormControlState,
153 | (ValidatorFn | ValidatorFn[] | AbstractControlOptions)?,
154 | (AsyncValidatorFn | AsyncValidatorFn[])?
155 | ]
156 | | FormControl;
157 |
158 | /**
159 | * The validation status of the control. There are four possible
160 | * validation status values:
161 | *
162 | * * **VALID**: This control has passed all validation checks.
163 | * * **INVALID**: This control has failed at least one validation check.
164 | * * **PENDING**: This control is in the midst of conducting a validation check.
165 | * * **DISABLED**: This control is exempt from validation checks.
166 | *
167 | * These status values are mutually exclusive, so a control cannot be
168 | * both valid AND invalid or invalid AND disabled.
169 | */
170 | export type Status = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';
171 |
172 | /**
173 | * Interface for options provided to an `AbstractControl`.
174 | */
175 | export interface AbstractControlOptions {
176 | /**
177 | * The list of validators applied to a control.
178 | */
179 | validators?: ValidatorFn | ValidatorFn[] | null;
180 | /**
181 | * The list of async validators applied to control.
182 | */
183 | asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null;
184 | /**
185 | * The event name for control to update upon.
186 | */
187 | updateOn?: 'change' | 'blur' | 'submit';
188 | }
189 |
190 | /**
191 | * A function that receives a control and synchronously returns a map of
192 | * validation errors if present, otherwise null.
193 | */
194 | export type ValidatorFn = (control: AbstractControl) => ValidationErrors | null;
195 |
196 | /**
197 | * A function that receives a control and returns a Promise or observable
198 | * that emits validation errors if present, otherwise null.
199 | */
200 | export type AsyncValidatorFn = (
201 | control: AbstractControl
202 | ) => Promise | null> | Observable | null>;
203 |
204 | /**
205 | * Defines the map of errors returned from failed validation checks.
206 | */
207 | export type ValidationErrors = T;
208 |
209 | /**
210 | * The default validators model, it includes almost all static properties of `Validators`,
211 | * excludes: `prototype`, `compose`, `composeAsync` and `nullValidator`.
212 | *
213 | * ### Usage
214 | *
215 | ```ts
216 | const formControl = new FormControl('some value');
217 | // OR
218 | const formGroup = new FormGroup({});
219 | // OR
220 | const formArray = new FormArray([]);
221 | ```
222 | */
223 | export class ValidatorsModel {
224 | min: { min: number; actual: number };
225 | max: { max: number; actual: number };
226 | required: true;
227 | email: true;
228 | minlength: { requiredLength: number; actualLength: number };
229 | maxlength: { requiredLength: number; actualLength: number };
230 | pattern: { requiredPattern: string; actualValue: string };
231 | fileRequired: { requiredSize: number; actualSize: number; file: File };
232 | filesMinLength: { requiredLength: number; actualLength: number };
233 | filesMaxLength: { requiredLength: number; actualLength: number };
234 | fileMaxSize: { requiredSize: number; actualSize: number; file: File };
235 | }
236 |
--------------------------------------------------------------------------------
/projects/forms/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # [3.1.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms-3.1.0) (2022-08-30)
3 |
4 | ### Features
5 |
6 | - [Added](https://github.com/KostyaTretyak/ng-stack/pull/110) support for Angular v14.
7 |
8 |
9 | ## [3.0.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms-3.0.0) (2022-02-17)
10 |
11 | ### BREAKING CHANGES
12 |
13 | - Changed module name to `NgsFormsModule`.
14 |
15 |
16 | ## [3.0.0-beta.2](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms-3.0.0-beta.2) (2022-01-07)
17 |
18 | ### Features
19 |
20 | - Changed `peerDependencies` to support angular v13.
21 |
22 |
23 | ## [2.4.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms-2.4.0) (2021-05-15)
24 |
25 | ### Features
26 |
27 | - Changed `peerDependencies` to support angular v12.
28 |
29 |
30 | ## [2.3.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.3.0) (2020-11-14)
31 |
32 | ### Features
33 |
34 | - Changed `peerDependencies` to support angular v11.
35 |
36 |
37 | ## [2.2.3](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.2.3) (2020-08-24)
38 |
39 | ### Bug Fixes
40 |
41 | - Fixed peerDependencies (see [#79](https://github.com/KostyaTretyak/ng-stack/issues/79)). `@ng-stack/forms` with version > 2.1.0 is not compatible with Angular v8 and below.
42 |
43 |
44 | ## [2.2.2](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.2.2) (2020-07-05)
45 |
46 | ### Bug Fixes
47 |
48 | - Fixed FormGroup API (see [70ea07](https://github.com/KostyaTretyak/ng-stack/commit/70ea0737bcf))
49 |
50 |
51 | ## [2.2.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.2.1) (2020-07-04)
52 |
53 | ### Bug Fixes
54 |
55 | - Fixed clearing form model from `Control`, closes [#76](https://github.com/KostyaTretyak/ng-stack/issues/76)
56 |
57 |
58 | ## [2.2.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.2.0) (2020-06-25)
59 |
60 | ### Features
61 |
62 | - **peer dependencies:** added support for Angular v10.
63 |
64 |
65 | ## [2.1.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.1.1) (2020-06-23)
66 |
67 | ### Bug Fixes
68 |
69 | - Fixed `` feature, see [#75](https://github.com/KostyaTretyak/ng-stack/issues/75).
70 |
71 |
72 | ## [2.1.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.1.0) (2020-06-23)
73 |
74 | ### Bug Fixes
75 |
76 | - Fixed `` feature, see [#75](https://github.com/KostyaTretyak/ng-stack/issues/75).
77 |
78 | ### Features
79 |
80 | - Added `preserveValue` option, see [docs](./README.md#preserveValue-option) and [#74](https://github.com/KostyaTretyak/ng-stack/issues/74)
81 |
82 |
83 | ## [2.0.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.0.1) (2020-06-03)
84 |
85 | ### Bug Fixes
86 |
87 | - Fixed first parameter type for `formArray.patchValue()` (value: `Partial
- []` -> `value: Item[]`), see [5bf943b](https://github.com/KostyaTretyak/ng-stack/commit/5bf943bfad4770e5ba26b4132ee6c53049922dde)
88 |
89 |
90 | ## [2.0.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.0.0) (2020-04-28)
91 |
92 | ### Notes
93 |
94 | - Release of stable version.
95 |
96 |
97 | ## [2.0.0-beta.4](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.0.0-beta.4) (2020-04-18)
98 |
99 | ### Feature
100 |
101 | - **Form builder** Added support for this signature:
102 | ```ts
103 | fb.group({
104 | id: {value: 1, disabled: true}
105 | name: [{value: '', disabled: false}]
106 | })
107 | ```
108 |
109 | (See [#67](https://github.com/KostyaTretyak/ng-stack/pull/67)).
110 |
111 |
112 | ## [2.0.0-beta.3](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.0.0-beta.3) (2020-04-09)
113 |
114 | ### Bug Fixes
115 |
116 | - **auto detecting controls types** Fixed issue with `Control`. (Closes [#64](https://github.com/KostyaTretyak/ng-stack/issues/64)).
117 |
118 |
119 | ## [2.0.0-beta.2](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.0.0-beta.2) (2020-03-28)
120 |
121 | ### Bug Fixes
122 |
123 | - **support strict mode for `ng build`** Removed `Required` for `reset()` and `setValue()`. (See [9e2408](https://github.com/KostyaTretyak/ng-stack/commit/9e2408)).
124 |
125 |
126 | ## [2.0.0-beta.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%402.0.0-beta.1) (2020-03-20)
127 |
128 | ### Features and BREAKING CHANGES
129 |
130 | - **support strict mode for `ng build`** Added support strict mode for `ng build`. (See [#45](https://github.com/KostyaTretyak/ng-stack/pull/45)).
131 | You cannot now partially pass fields in `FormBuilder` or `FormGroup` unless the form model has optional fields.
132 |
133 | For example:
134 |
135 | ```ts
136 | import { FormBuilder } from '@ng-stack/forms';
137 |
138 | // Form model
139 | class Address {
140 | city: string;
141 | street: string; // If you make this field optional, the error will disappear.
142 | }
143 |
144 | const formBuilder = new FormBuilder();
145 |
146 | formBuilder.group({
147 | city: 'Mykolaiv',
148 | });
149 |
150 | // Argument of type '{ city: string; }' is not assignable to parameter of type ...
151 | // Property 'street' is missing in type '{ city: string; }' but required in type ...
152 | ```
153 |
154 |
155 | ## [1.4.0-beta](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.4.0-beta) (2020-03-11)
156 |
157 | ### Features
158 |
159 | - **support Control with an object model type** See [Automatically detect appropriate types for form controls](README.md#automatically-detect-appropriate-types-for-form-controls) (See [commit](https://github.com/KostyaTretyak/ng-stack/commit/faafda)).
160 |
161 |
162 | ## [1.3.4](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.3.4) (2020-01-08)
163 |
164 | ### Fix peer dependencies
165 |
166 | - **install the library** Now peer dependencies included v8 of `@angular/core`.
167 |
168 |
169 | ## [1.3.3](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.3.3) (2019-07-23)
170 |
171 | ### Bug Fixes
172 |
173 | - **validation model:** Fixed default `ValidatorsModel`. ([#53](https://github.com/KostyaTretyak/ng-stack/pull/53)).
174 |
175 |
176 | ## [1.3.2](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.3.2) (2019-05-09)
177 |
178 | ### Bug Fixes and BREAKING CHANGES
179 |
180 | - **Control type:** Removed buggy `Control` type support. ([#48](https://github.com/KostyaTretyak/ng-stack/pull/48)).
181 |
182 |
183 | ## [1.3.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.3.1) (2019-05-01)
184 |
185 | ### Bug Fixes
186 |
187 | - **validation model:** fixed issues with nested validation models ([#44](https://github.com/KostyaTretyak/ng-stack/pull/44)).
188 |
189 |
190 | ## [1.3.0](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.3.0) (2019-04-17)
191 |
192 | ### Features
193 |
194 | - **types:** added support for union types ([#39](https://github.com/KostyaTretyak/ng-stack/pull/39)).
195 |
196 |
197 |
198 | ## [1.2.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.2.1) (2019-04-09)
199 |
200 | ### Features and BREAKING CHANGES
201 |
202 | - **input[type=file] directive:** added new @Output `select` event,
203 | and removed @Output `selectedFiles` event ([#35](https://github.com/KostyaTretyak/ng-stack/pull/35)).
204 |
205 |
206 |
207 | ## [1.2.0-beta.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.2.0-beta.1) (2019-04-07)
208 |
209 | ### Features and BREAKING CHANGES
210 |
211 | - **input[type=file] directive:** added new @Output `selectedFiles` event,
212 | and removed @Input() `valueAsFileList` ([#32](https://github.com/KostyaTretyak/ng-stack/pull/32)).
213 |
214 |
215 |
216 | ## [1.1.0-beta.2](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.1.0-beta.2) (2019-04-06)
217 |
218 | ### Bug Fixes
219 |
220 | - **Validators:** fixed filesMinLength and filesMaxLength methods names ([#29](https://github.com/KostyaTretyak/ng-stack/pull/29)).
221 |
222 |
223 |
224 | ## [1.1.0-beta.1](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.1.0-beta.1) (2019-04-06)
225 |
226 | ### Features
227 |
228 | - **new directive:** added support for `input[type="file"]` ([#25](https://github.com/KostyaTretyak/ng-stack/pull/25)).
229 |
230 |
231 |
232 | ## [1.0.0-beta.6](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.0.0-beta.6) (2019-03-10)
233 |
234 | ### Bug Fixes
235 |
236 | - **form model:** Type mismatch ([#18](https://github.com/KostyaTretyak/ng-stack/issues/18))
237 |
238 |
239 |
240 | ## [1.0.0-beta.5](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.0.0-beta.5) (2019-03-09)
241 |
242 | ### Bug Fixes
243 |
244 | - **validation model:** Issue with interpreting of a validation model ([#15](https://github.com/KostyaTretyak/ng-stack/issues/15))
245 |
246 |
247 |
248 | ## [1.0.0-beta.4](https://github.com/KostyaTretyak/ng-stack/releases/tag/forms%401.0.0-beta.4) (2019-03-08)
249 |
250 | ### Features
251 |
252 | - **validation model:** added support for a validation model ([#14](https://github.com/KostyaTretyak/ng-stack/pull/14)).
253 |
254 |
255 |
256 | ## 0.0.0-alpha.1 (2019-02-25)
257 |
258 | ### Features
259 |
260 | - **npm pack:** `@ng-stack/forms` use Angular-CLI v7 git mono repository and build npm pack with it.
261 |
--------------------------------------------------------------------------------
/projects/forms/src/lib/form-control.spec.ts:
--------------------------------------------------------------------------------
1 | import { AbstractControl } from '@angular/forms';
2 |
3 | import { FormControl } from './form-control';
4 | import { isString, isNumber, isArray, isObject } from './assert';
5 | import { FormGroup } from './form-group';
6 | import { ValidatorFn, Control } from './types';
7 |
8 | describe('FormControl', () => {
9 | xdescribe('checking types only', () => {
10 | describe('constructor()', () => {
11 | isString(new FormControl('').value);
12 | isString(new FormControl('').value);
13 | isNumber(new FormControl(1).value);
14 | isNumber(new FormControl(1).value);
15 | isArray(new FormControl([]).value);
16 | isArray(
17 | new FormControl([1]).value
18 | );
19 | isObject(new FormControl({}).value);
20 | isObject(new FormControl