├── .prettierignore
├── packages
└── core
│ ├── styles
│ └── validator.scss
│ ├── src
│ ├── version.ts
│ ├── testing
│ │ ├── index.ts
│ │ ├── dispatcher-events.ts
│ │ └── events.ts
│ ├── helpers.ts
│ ├── strategies
│ │ ├── validation-feedback-strategy.ts
│ │ ├── noop-validation-feedback-strategy.ts
│ │ ├── index.ts
│ │ └── bootstrap-validation-feedback-strategy.ts
│ ├── public-api.ts
│ ├── test.ts
│ ├── validator.service.spec.ts
│ ├── directives
│ │ ├── form-submit.directive.ts
│ │ ├── validators.ts
│ │ ├── form-validator.directive.ts
│ │ ├── validators.spec.ts
│ │ └── form-validator.directive.spec.ts
│ ├── validators.ts
│ ├── message-transformers.ts
│ ├── validator.class.ts
│ ├── module.ts
│ ├── validators.spec.ts
│ ├── validator-loader.service.ts
│ ├── validator-loader.service.spec.ts
│ ├── module.spec.ts
│ └── validator.service.ts
│ ├── examples
│ ├── form-validator
│ │ ├── examples
│ │ │ ├── reactive-driven
│ │ │ │ ├── reactive-driven.component.scss
│ │ │ │ ├── index.md
│ │ │ │ ├── reactive-driven.component.ts
│ │ │ │ └── reactive-driven.component.html
│ │ │ ├── template-driven
│ │ │ │ ├── index.md
│ │ │ │ ├── template-driven.component.scss
│ │ │ │ ├── custom-select
│ │ │ │ │ ├── custom-select.component.scss
│ │ │ │ │ ├── custom-select.component.html
│ │ │ │ │ ├── custom-select.component.spec.ts
│ │ │ │ │ └── custom-select.component.ts
│ │ │ │ ├── template-driven.component.ts
│ │ │ │ └── template-driven.component.html
│ │ │ └── module.ts
│ │ ├── doc
│ │ │ ├── zh-cn.md
│ │ │ └── en-us.md
│ │ └── api
│ │ │ └── en-us.js
│ └── validators
│ │ ├── examples
│ │ ├── max
│ │ │ ├── max.component.scss
│ │ │ ├── max.component.ts
│ │ │ └── max.component.html
│ │ ├── min
│ │ │ ├── min.component.scss
│ │ │ ├── min.component.ts
│ │ │ └── min.component.html
│ │ ├── unique-check
│ │ │ ├── unique-check.component.scss
│ │ │ ├── unique-check.component.ts
│ │ │ └── unique-check.component.html
│ │ └── module.ts
│ │ └── doc
│ │ ├── zh-cn.md
│ │ └── en-us.md
│ ├── ng-package.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ ├── tslint.json
│ ├── package.json
│ ├── tsconfig.lib.json
│ └── karma.conf.js
├── .coveralls.yml
├── commitlint.config.js
├── .vscode
└── settings.json
├── .docgeni
└── public
│ ├── assets
│ └── favicon.ico
│ ├── index.html
│ ├── tsconfig.json
│ └── styles.scss
├── Roadmap.md
├── .editorconfig
├── .browserslistrc
├── .prettierrc
├── .wpmrc.js
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── tsconfig.json
├── .circleci
└── config.yml
├── LICENSE
├── .all-contributorsrc
├── angular.json
├── .docgenirc.js
├── package.json
├── README.md
├── docs
├── zh-cn
│ └── index.md
└── index.md
├── CHANGELOG.md
└── 1.0.0-publish.md
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.md
2 | *.json
3 |
--------------------------------------------------------------------------------
/packages/core/styles/validator.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/core/src/version.ts:
--------------------------------------------------------------------------------
1 | export const VERSION = '16.0.0';
2 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-pro
2 | repo_token: 4dsZWc9Ld1NRky4Li80sZBPZ6fmnoa6W3
3 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] };
2 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/reactive-driven/reactive-driven.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/reactive-driven/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 20
3 | ---
4 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 10
3 | ---
4 |
--------------------------------------------------------------------------------
/packages/core/src/testing/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dispatcher-events';
2 | export * from './events';
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Injectable",
4 | "Validators"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.docgeni/public/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/why520crazy/ngx-validator/HEAD/.docgeni/public/assets/favicon.ico
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/max/max.component.scss:
--------------------------------------------------------------------------------
1 | .btn-groups {
2 | .btn {
3 | margin-right: 15px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/min/min.component.scss:
--------------------------------------------------------------------------------
1 | .btn-groups {
2 | .btn {
3 | margin-right: 15px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/helpers.ts:
--------------------------------------------------------------------------------
1 | export function isFunction(value: any) {
2 | const type = typeof value;
3 | return !!value && type === 'function';
4 | }
5 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/unique-check/unique-check.component.scss:
--------------------------------------------------------------------------------
1 | .btn-groups {
2 | .btn {
3 | margin-right: 15px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/template-driven.component.scss:
--------------------------------------------------------------------------------
1 | .btn-groups {
2 | .btn {
3 | margin-right: 15px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/core",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Roadmap.md:
--------------------------------------------------------------------------------
1 | ## ngx-validator Roadmap
2 |
3 | - Add more validators, e.g. ngx-min, ngx-max, ngx-repeat, ngx-unique-check ngx-range
4 | - Add Reactive Forms Demo and test
5 | - Add trigger condition `validateOn`, support `submit`、`blur`
6 |
--------------------------------------------------------------------------------
/packages/core/src/strategies/validation-feedback-strategy.ts:
--------------------------------------------------------------------------------
1 | export interface ValidationFeedbackStrategy {
2 | showError(element: HTMLElement, errorMessages: string[]): void;
3 | removeError(element: HTMLElement): void;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.lib.json",
3 | "compilerOptions": {
4 | "declarationMap": false
5 | },
6 | "angularCompilerOptions": {
7 | "compilationMode": "partial"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/custom-select/custom-select.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | flex-direction: column;
4 | .item {
5 | padding: 10px 10px;
6 | display: flex;
7 | cursor: pointer;
8 | }
9 | }
10 | .active {
11 | background: #eee;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/doc/zh-cn.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Form Validator
3 | name: validator
4 | ---
5 |
6 | `[ngxFormValidator]` 在表单上配置验证规则
7 |
8 | `(ngxFormSubmit)` 验证成功后触发提交事件
9 |
10 | ## 模板驱动
11 |
12 |
13 | ## 响应式驱动
14 |
15 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | true,
6 | "attribute",
7 | "w5c",
8 | "ngx",
9 | "camelCase"
10 | ],
11 | "component-selector": [
12 | true,
13 | "element",
14 | "ngx",
15 | "test",
16 | "kebab-case"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.json]
12 | indent_size = 2
13 |
14 | [*.html]
15 | indent_size = 2
16 |
17 | [*.md]
18 | max_line_length = off
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.docgeni/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ngx Validator
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/custom-select/custom-select.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ option.nativeElement.text }}
10 |
11 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/doc/zh-cn.md:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | `NgxValidator` 提供了如下几种验证器
5 | - `ngxMax` 数字最大值
6 | - `ngxMin` 数字最小值
7 | - `ngxUniqueCheck` 远程唯一性验证,比如:验证用户名是否存在
8 |
9 | ## Max
10 |
11 |
12 |
13 | ## Min
14 |
15 |
16 |
17 | ## Unique Check
18 |
19 |
20 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/doc/en-us.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Form Validator
3 | name: validator
4 | ---
5 |
6 | `[ngxFormValidator]` provide form validation config and validate feature.
7 |
8 | `(ngxFormSubmit)` provide submit event when validate success
9 |
10 | ## Template Driven
11 |
12 |
13 | ## Reactive Driven
14 |
15 |
--------------------------------------------------------------------------------
/packages/core/src/strategies/noop-validation-feedback-strategy.ts:
--------------------------------------------------------------------------------
1 | import { ValidationFeedbackStrategy } from './validation-feedback-strategy';
2 |
3 | export class NoopValidationFeedbackStrategy implements ValidationFeedbackStrategy {
4 | /** Does nothing, as this validation message display strategy is a no-op. */
5 | showError(element: HTMLElement, errorMessages: string[]): void {}
6 |
7 | removeError(element: HTMLElement): void {}
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/doc/en-us.md:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | `NgxValidator` support some validators:
5 | - `ngxMax` number max validator
6 | - `ngxMin` number min validator
7 | - `ngxUniqueCheck` remote unique check, e.g. username
8 |
9 | ## Max
10 |
11 |
12 |
13 | ## Min
14 |
15 |
16 |
17 | ## Unique Check
18 |
19 |
20 |
--------------------------------------------------------------------------------
/packages/core/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of core
3 | */
4 |
5 | export * from './module';
6 | export * from './validator-loader.service';
7 | export * from './validator.class';
8 | export * from './directives/form-validator.directive';
9 | export * from './directives/validators';
10 | export * from './directives/form-submit.directive';
11 | export * from './strategies';
12 | export { NgxValidators } from './validators';
13 | export * from './version';
14 |
--------------------------------------------------------------------------------
/.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 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "eslintIntegration": true,
3 | "stylelintIntegration": true,
4 | "tabWidth": 4,
5 | "singleQuote": true,
6 | "semi": true,
7 | "printWidth": 120,
8 | "overrides": [
9 | {
10 | "files": "*.json",
11 | "options": {
12 | "tabWidth": 2
13 | }
14 | },
15 | {
16 | "files": "*.html",
17 | "options": {
18 | "tabWidth": 2
19 | }
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/.wpmrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | allowBranch: ['master'],
3 | bumpFiles: [
4 | 'package.json',
5 | './packages/core/package.json',
6 | {
7 | filename: './packages/core/src/version.ts',
8 | type: 'code'
9 | }
10 | ],
11 | skip: {
12 | changelog: true
13 | },
14 | commitAll: true,
15 | hooks: {
16 | prepublish: 'npm run build',
17 | postreleaseBranch: 'git add .',
18 | postpublish: 'npm run pub-only'
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/packages/core/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'core-js/es7/reflect';
4 | import 'zone.js';
5 | import 'zone.js/testing';
6 | import { getTestBed } from '@angular/core/testing';
7 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
8 |
9 | // First, initialize the Angular testing environment.
10 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
11 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI NGX-VALIDATOR through Github Actions
2 | on: push
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v2
8 | - name: Use Node.js 16.x
9 | uses: actions/setup-node@v1
10 | with:
11 | node-version: 16.x
12 |
13 | - name: Setup
14 | run: npm ci
15 |
16 | - name: Test
17 | run: |
18 | npm test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
19 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@why520crazy/ngx-validator",
3 | "version": "16.0.0",
4 | "description": "Angular7+ form validator, make error tips easy and automatic. don't need to manually write error tips templates.",
5 | "private": false,
6 | "repository": "https://github.com/why520crazy/ngx-validator",
7 | "author": "why520crazy ",
8 | "license": "MIT",
9 | "dependencies": {
10 | "tslib": "^2.0.0"
11 | },
12 | "peerDependencies": {
13 | "@angular/common": "*",
14 | "@angular/forms": "*",
15 | "@angular/core": "*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/validator.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { NgxValidatorLoader } from './validator-loader.service';
3 |
4 | import { NgxFormValidatorService } from './validator.service';
5 |
6 | describe('NgxFormValidatorService', () => {
7 | beforeEach(() =>
8 | TestBed.configureTestingModule({
9 | providers: [NgxFormValidatorService, NgxValidatorLoader]
10 | })
11 | );
12 |
13 | it('should be created', () => {
14 | const service: NgxFormValidatorService = TestBed.inject(NgxFormValidatorService);
15 | expect(service).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/max/max.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { NgxValidatorConfig } from '@why520crazy/ngx-validator';
3 |
4 | @Component({
5 | selector: 'ngx-validators-max-example',
6 | templateUrl: './max.component.html',
7 | styleUrls: ['./max.component.scss']
8 | })
9 | export class NgxValidatorsMaxExampleComponent implements OnInit {
10 | validatorConfig: NgxValidatorConfig;
11 |
12 | value: number;
13 |
14 | message: string;
15 |
16 | constructor() {}
17 |
18 | ngOnInit(): void {}
19 |
20 | submit() {
21 | this.message = 'This form has submit';
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/min/min.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { NgxValidatorConfig } from '@why520crazy/ngx-validator';
3 |
4 | @Component({
5 | selector: 'ngx-validators-min-example',
6 | templateUrl: './min.component.html',
7 | styleUrls: ['./min.component.scss']
8 | })
9 | export class NgxValidatorsMinExampleComponent implements OnInit {
10 | validatorConfig: NgxValidatorConfig;
11 |
12 | value: number;
13 |
14 | message: string;
15 |
16 | constructor() {}
17 |
18 | ngOnInit(): void {}
19 |
20 | submit() {
21 | this.message = 'This form has submit';
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.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 | built
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.angular/cache
30 | /.sass-cache
31 | /connect.lock
32 | /coverage
33 | /libpeerconnection.log
34 | npm-debug.log
35 | yarn-error.log
36 | testem.log
37 | /typings
38 |
39 | # System Files
40 | .DS_Store
41 | Thumbs.db
42 | .docgeni/site
--------------------------------------------------------------------------------
/packages/core/src/strategies/index.ts:
--------------------------------------------------------------------------------
1 | import { NoopValidationFeedbackStrategy } from './noop-validation-feedback-strategy';
2 | import { BootstrapValidationFeedbackStrategy } from './bootstrap-validation-feedback-strategy';
3 | import { ValidationFeedbackStrategy } from './validation-feedback-strategy';
4 |
5 | export class ValidationFeedbackStrategyBuilder {
6 | static noop(): ValidationFeedbackStrategy {
7 | return new NoopValidationFeedbackStrategy();
8 | }
9 |
10 | static bootstrap(): ValidationFeedbackStrategy {
11 | return new BootstrapValidationFeedbackStrategy();
12 | }
13 | }
14 |
15 | export { ValidationFeedbackStrategy, NoopValidationFeedbackStrategy, BootstrapValidationFeedbackStrategy };
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "downlevelIteration": true,
6 | "importHelpers": true,
7 | "outDir": "./dist/out-tsc",
8 | "sourceMap": true,
9 | "declaration": false,
10 | "module": "es2020",
11 | "moduleResolution": "node",
12 | "experimentalDecorators": true,
13 | "target": "ES2022",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ],
21 | "paths": {
22 | "core": [
23 | "dist/core"
24 | ],
25 | "core/*": [
26 | "dist/core/*"
27 | ]
28 | },
29 | "useDefineForClassFields": false
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "declarationMap": true,
6 | "module": "es2015",
7 | "moduleResolution": "node",
8 | "declaration": true,
9 | "sourceMap": true,
10 | "inlineSources": true,
11 | "experimentalDecorators": true,
12 | "importHelpers": true,
13 | "types": [],
14 | "lib": ["dom", "es2018"]
15 | },
16 | "angularCompilerOptions": {
17 | "skipTemplateCodegen": true,
18 | "strictMetadataEmit": true,
19 | "fullTemplateTypeCheck": true,
20 | "strictInjectionParameters": true,
21 | "enableResourceInlining": true
22 | },
23 | "exclude": ["src/test.ts", "**/*.spec.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | browser-tools: circleci/browser-tools@1.4.3
4 | jobs:
5 | build:
6 | working_directory: ~/ngx-validator
7 | docker:
8 | - image: cimg/node:18.16-browsers
9 | steps:
10 | - browser-tools/install-chrome
11 | - checkout
12 | - restore_cache:
13 | key: ngx-validator-{{ .Branch }}-{{ checksum "package-lock.json" }}
14 | - run: npm install
15 | - save_cache:
16 | key: ngx-validator-{{ .Branch }}-{{ checksum "package-lock.json" }}
17 | paths:
18 | - 'node_modules'
19 | - run: npm run test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
20 | - run: npm run report-coverage
21 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/unique-check/unique-check.component.ts:
--------------------------------------------------------------------------------
1 | import { NgxValidatorConfig } from '@why520crazy/ngx-validator';
2 | import { Component, OnInit } from '@angular/core';
3 | import { of } from 'rxjs';
4 |
5 | @Component({
6 | selector: 'ngx-validators-unique-check-example',
7 | templateUrl: './unique-check.component.html',
8 | styleUrls: ['./unique-check.component.scss']
9 | })
10 | export class NgxValidatorsUniqueCheckExampleComponent implements OnInit {
11 | validatorConfig: NgxValidatorConfig;
12 |
13 | value: number;
14 |
15 | message: string;
16 |
17 | uniqueCheck = (value: string) => {
18 | return value === 'peter' ? of(true) : of(false);
19 | };
20 |
21 | constructor() {}
22 |
23 | ngOnInit(): void {}
24 |
25 | submit() {
26 | this.message = 'This form has submit';
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { NgxValidatorModule } from '@why520crazy/ngx-validator';
4 | import { ReactiveFormsModule } from '@angular/forms';
5 | import { NgxValidatorsMinExampleComponent } from './min/min.component';
6 | import { NgxValidatorsMaxExampleComponent } from './max/max.component';
7 | import { NgxValidatorsUniqueCheckExampleComponent } from './unique-check/unique-check.component';
8 |
9 | @NgModule({
10 | declarations: [
11 | NgxValidatorsMinExampleComponent,
12 | NgxValidatorsMaxExampleComponent,
13 | NgxValidatorsUniqueCheckExampleComponent
14 | ],
15 | imports: [CommonModule, NgxValidatorModule, ReactiveFormsModule],
16 | exports: [],
17 | providers: []
18 | })
19 | export class NgxValidatorsExamplesModule {}
20 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/max/max.component.html:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/min/min.component.html:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/packages/core/examples/validators/examples/unique-check/unique-check.component.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/packages/core/src/directives/form-submit.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, Output, OnInit, HostBinding, HostListener, Optional, EventEmitter } from '@angular/core';
2 | import { NgForm } from '@angular/forms';
3 | import { NgxFormValidatorDirective } from './form-validator.directive';
4 |
5 | @Directive({
6 | selector: '[ngxFormSubmit],[ngx-form-submit]'
7 | })
8 | export class NgxFormSubmitDirective implements OnInit {
9 |
10 | @Output() ngxFormSubmit = new EventEmitter();
11 |
12 | constructor(
13 | private validatorDirective: NgxFormValidatorDirective
14 | ) {
15 | }
16 |
17 | ngOnInit(): void {
18 | this.validatorDirective.onSubmitSuccess = ($event: any) => {
19 | this.ngxFormSubmit.emit($event);
20 | };
21 | }
22 |
23 | @HostListener('click', ['$event'])
24 | onSubmit($event: any) {
25 | this.validatorDirective.submit($event);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/custom-select/custom-select.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 |
3 | import { CustomSelectComponent } from './custom-select.component';
4 |
5 | describe('CustomSelectComponent', () => {
6 | let component: CustomSelectComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(
10 | waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [CustomSelectComponent]
13 | }).compileComponents();
14 | })
15 | );
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(CustomSelectComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | });
22 |
23 | it('should create', () => {
24 | expect(component).toBeTruthy();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/.docgeni/public/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "outDir": "./dist/out-tsc/site",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "downlevelIteration": true,
8 | "experimentalDecorators": true,
9 | "module": "ESNext",
10 | "moduleResolution": "node",
11 | "esModuleInterop": false,
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "lib": [
15 | "es2018",
16 | "dom"
17 | ],
18 | "types": [
19 | "node"
20 | ],
21 | "paths":{
22 | "@why520crazy/ngx-validator/*":["../../packages/core/src/*"],
23 | "@why520crazy/ngx-validator":["../../packages/core/src/index.ts","../../packages/core/src/public-api.ts"],
24 | }
25 | },
26 | "files": [
27 | "src/main.ts",
28 | "src/polyfills.ts"
29 | ],
30 | "include": [
31 | "src/**/*.d.ts"
32 | ],
33 | "exclude": [
34 | "src/assets",
35 | "test.ts"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { NgxValidatorModule } from '@why520crazy/ngx-validator';
4 | import { NgxValidatorTemplateDrivenExampleComponent } from './template-driven/template-driven.component';
5 | import { NgxValidatorReactiveDrivenExampleComponent } from './reactive-driven/reactive-driven.component';
6 | import { ReactiveFormsModule } from '@angular/forms';
7 | import { CustomSelectComponent } from './template-driven/custom-select/custom-select.component';
8 |
9 | @NgModule({
10 | declarations: [
11 | NgxValidatorTemplateDrivenExampleComponent,
12 | NgxValidatorReactiveDrivenExampleComponent,
13 | CustomSelectComponent
14 | ],
15 | imports: [CommonModule, NgxValidatorModule, ReactiveFormsModule],
16 | exports: [],
17 | providers: []
18 | })
19 | export class NgxValidatorExamplesModule {}
20 |
--------------------------------------------------------------------------------
/packages/core/src/validators.ts:
--------------------------------------------------------------------------------
1 | import { AbstractControl, ValidationErrors } from '@angular/forms';
2 | import { of, Observable } from 'rxjs';
3 | import { map } from 'rxjs/operators';
4 |
5 | export class NgxValidators {
6 | static uniqueCheckValidator(
7 | uniqueCheckFn: (value: string) => Observable
8 | ): (control: AbstractControl) => Promise | Observable {
9 | const result = (
10 | control: AbstractControl
11 | ): Promise | Observable => {
12 | if (control.value) {
13 | return uniqueCheckFn(control.value).pipe(
14 | map(isUnique => {
15 | return isUnique ? { ngxUniqueCheck: { value: true } } : null;
16 | })
17 | );
18 | } else {
19 | return of(null);
20 | }
21 | };
22 | return result;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 why520crazy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/core/src/message-transformers.ts:
--------------------------------------------------------------------------------
1 | function maxOrMinLengthTransformer(message: string, validationErrorValues: { requiredLength: number }): string {
2 | return message.replace(`{requiredLength}`, validationErrorValues.requiredLength.toString());
3 | }
4 |
5 | function maxTransformer(message: string, validationErrorValues: { max: number; actual: number }): string {
6 | return message.replace(`{max}`, validationErrorValues.max.toString());
7 | }
8 |
9 | function minxTransformer(message: string, validationErrorValues: { min: number; actual: number }): string {
10 | return message.replace(`{min}`, validationErrorValues.min.toString());
11 | }
12 |
13 | const transformerMap = {
14 | minlength: maxOrMinLengthTransformer,
15 | maxlength: maxOrMinLengthTransformer,
16 | max: maxTransformer,
17 | min: minxTransformer
18 | };
19 |
20 | export function transformMessage(validatorName: string, message: string, validationErrorValues: any) {
21 | if (transformerMap[validatorName] && validationErrorValues) {
22 | return transformerMap[validatorName](message, validationErrorValues);
23 | }
24 | return message;
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/validator.class.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 | import { ValidationFeedbackStrategy } from './strategies';
3 |
4 | export declare type NgxValidationMessages = Record>;
5 |
6 | export declare type NgxValidateOn = 'submit' | 'blur' | 'change';
7 |
8 | export interface NgxValidatorConfig {
9 | validationFeedbackStrategy?: ValidationFeedbackStrategy;
10 | validationMessages?: NgxValidationMessages;
11 | validateOn?: NgxValidateOn;
12 | }
13 | export interface NgxValidatorGlobalConfig extends NgxValidatorConfig {
14 | globalValidationMessages?: Record;
15 | }
16 |
17 | export const NGX_VALIDATOR_CONFIG = new InjectionToken('NGX_VALIDATION_CONFIG');
18 |
19 | export const DEFAULT_GLOBAL_VALIDATION_MESSAGES = {
20 | required: '该选项不能为空',
21 | maxlength: '该选项输入值长度不能大于{requiredLength}',
22 | minlength: '该选项输入值长度不能小于{requiredLength}',
23 | ngxUniqueCheck: '输入值已经存在,请重新输入',
24 | email: '输入邮件的格式不正确',
25 | repeat: '两次输入不一致',
26 | pattern: '该选项输入格式不正确',
27 | number: '必须输入数字',
28 | url: '输入URL格式不正确',
29 | max: '该选项输入值不能大于{max}',
30 | min: '该选项输入值不能小于{min}'
31 | };
32 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "@why520crazy/ngx-validator",
3 | "projectOwner": "why520carzy",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "none",
12 | "contributors": [
13 | {
14 | "login": "why520crazy",
15 | "name": "why520crazy",
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/3959960?v=4",
17 | "profile": "https://www.zhihu.com/people/why520crazy/activities",
18 | "contributions": [
19 | "question"
20 | ]
21 | },
22 | {
23 | "login": "luxiaobei",
24 | "name": "luxiaobei",
25 | "avatar_url": "https://avatars1.githubusercontent.com/u/13583957?v=4",
26 | "profile": "https://github.com/luxiaobei",
27 | "contributions": [
28 | "code"
29 | ]
30 | },
31 | {
32 | "login": "walkerkay",
33 | "name": "Walker",
34 | "avatar_url": "https://avatars1.githubusercontent.com/u/15701592?v=4",
35 | "profile": "https://github.com/walkerkay",
36 | "contributions": [
37 | "design"
38 | ]
39 | }
40 | ],
41 | "contributorsPerLine": 7
42 | }
43 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "core": {
7 | "root": "packages/core",
8 | "sourceRoot": "packages/core/src",
9 | "projectType": "library",
10 | "prefix": "ngx",
11 | "architect": {
12 | "build": {
13 | "builder": "@angular-devkit/build-angular:ng-packagr",
14 | "options": {
15 | "tsConfig": "packages/core/tsconfig.lib.json",
16 | "project": "packages/core/ng-package.json"
17 | },
18 | "configurations": {
19 | "production": {
20 | "tsConfig": "packages/core/tsconfig.lib.prod.json"
21 | }
22 | }
23 | },
24 | "test": {
25 | "builder": "@angular-devkit/build-angular:karma",
26 | "options": {
27 | "main": "packages/core/src/test.ts",
28 | "tsConfig": "packages/core/tsconfig.spec.json",
29 | "karmaConfig": "packages/core/karma.conf.js",
30 | "codeCoverage": true,
31 | "codeCoverageExclude": ["packages/core/src/testing/**/*"]
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/api/en-us.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | type: 'directive',
4 | name: '[ngxFormValidator]',
5 | description: 'Form validator',
6 | properties: [
7 | {
8 | name: 'ngxFormValidator',
9 | type: `NgxValidatorConfig`,
10 | default: `null`,
11 | description: 'validator congif'
12 | }
13 | ]
14 | },
15 | {
16 | type: 'interface',
17 | name: 'NgxValidatorConfig',
18 | description: 'Validator Config',
19 | properties: [
20 | {
21 | name: 'validateOn',
22 | type: `'submit' | 'blur' | 'change'`,
23 | default: `submit`,
24 | description: 'validate trigger'
25 | },
26 | {
27 | name: 'validationMessages',
28 | type: `Record>`,
29 | default: `null`,
30 | description: `validation messages, e.g. \n { username: { required: 'username is required'}`
31 | },
32 | {
33 | name: 'validationFeedbackStrategy',
34 | type: `ValidationFeedbackStrategy`,
35 | default: `null`,
36 | description: `validation feedback strategy`
37 | }
38 | ]
39 | }
40 | ];
41 |
--------------------------------------------------------------------------------
/packages/core/src/module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ModuleWithProviders } from '@angular/core';
2 | import { FormsModule } from '@angular/forms';
3 | import { NgxFormValidatorDirective } from './directives/form-validator.directive';
4 | import { NgxFormSubmitDirective } from './directives/form-submit.directive';
5 |
6 | // import { NgxUniqueCheckDirective } from './directives/form-unique-check.directive';
7 | import { MaxValidatorDirective, MinValidatorDirective, NgxUniqueCheckDirective } from './directives/validators';
8 |
9 | import { NgxValidatorGlobalConfig, NGX_VALIDATOR_CONFIG } from './validator.class';
10 | import { NgxValidatorLoader } from './validator-loader.service';
11 |
12 | const declarations = [
13 | NgxFormValidatorDirective,
14 | NgxFormSubmitDirective,
15 | NgxUniqueCheckDirective,
16 | MaxValidatorDirective,
17 | MinValidatorDirective
18 | ];
19 |
20 | @NgModule({
21 | declarations: declarations,
22 | providers: [NgxValidatorLoader],
23 | imports: [FormsModule],
24 | exports: [...declarations, FormsModule]
25 | })
26 | export class NgxValidatorModule {
27 | static forRoot(config: NgxValidatorGlobalConfig): ModuleWithProviders {
28 | return {
29 | ngModule: NgxValidatorModule,
30 | providers: [
31 | {
32 | provide: NGX_VALIDATOR_CONFIG,
33 | useValue: config
34 | }
35 | ]
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.docgeni/public/styles.scss:
--------------------------------------------------------------------------------
1 | @import 'bootstrap/scss/bootstrap.scss';
2 | @import '@docgeni/template/styles/index.css';
3 |
4 | @mixin ngx-form-validation-state($state, $color) {
5 | .ngx-custom-select {
6 | .was-validated &:#{$state},
7 | &.is-#{$state} {
8 | // border-color: $color;
9 | border: 1px solid $color;
10 |
11 | @if $enable-validation-icons {
12 | padding-right: $input-height-inner;
13 | background-repeat: no-repeat;
14 | background-position: center right calc(#{$input-height-inner} / 4);
15 | background-size: calc(#{$input-height-inner} / 2) calc(#{$input-height-inner} / 2);
16 |
17 | @if $state == 'valid' {
18 | background-image: $form-feedback-icon-valid;
19 | } @else {
20 | background-image: $form-feedback-icon-invalid;
21 | }
22 | }
23 |
24 | &:focus {
25 | border-color: $color;
26 | box-shadow: 0 0 0 $input-focus-width rgba($color, 0.25);
27 | }
28 |
29 | ~ .#{$state}-feedback,
30 | ~ .#{$state}-tooltip {
31 | display: block;
32 | }
33 | }
34 | }
35 | }
36 |
37 | @include ngx-form-validation-state('valid', $form-feedback-valid-color);
38 | @include ngx-form-validation-state('invalid', $form-feedback-invalid-color);
39 |
40 | a:hover {
41 | text-decoration: none;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/core/src/strategies/bootstrap-validation-feedback-strategy.ts:
--------------------------------------------------------------------------------
1 | import { ValidationFeedbackStrategy } from './validation-feedback-strategy';
2 |
3 | const INVALID_CLASS = 'is-invalid';
4 | const INVALID_FEEDBACK_CLASS = 'invalid-feedback';
5 |
6 | export class BootstrapValidationFeedbackStrategy implements ValidationFeedbackStrategy {
7 | constructor() {}
8 |
9 | showError(element: HTMLElement, errorMessages: string[]): void {
10 | if (element) {
11 | element.classList.add(INVALID_CLASS);
12 | }
13 |
14 | if (element && element.parentElement) {
15 | const documentFrag = document.createDocumentFragment();
16 | const divNode = document.createElement('DIV');
17 | const textNode = document.createTextNode(errorMessages[0]);
18 | divNode.appendChild(textNode);
19 | divNode.setAttribute('class', INVALID_FEEDBACK_CLASS);
20 | documentFrag.appendChild(divNode);
21 | element.parentElement.append(documentFrag);
22 | }
23 | }
24 |
25 | removeError(element: HTMLElement): void {
26 | if (element) {
27 | element.classList.remove(INVALID_CLASS);
28 | }
29 | if (element && element.parentElement) {
30 | const invalidFeedback = element.parentElement.querySelector(`.${INVALID_FEEDBACK_CLASS}`);
31 | if (invalidFeedback) {
32 | element.parentElement.removeChild(invalidFeedback);
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/testing/dispatcher-events.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createFakeEvent,
3 | createKeyboardEvent,
4 | createMouseEvent,
5 | createTouchEvent
6 | } from './events';
7 |
8 | /** Utility to dispatch any event on a Node. */
9 | export function dispatchEvent(node: Node | Window, event: Event): Event {
10 | node.dispatchEvent(event);
11 | return event;
12 | }
13 |
14 | /** Shorthand to dispatch a fake event on a specified node. */
15 | export function dispatchFakeEvent(
16 | node: Node | Window,
17 | type: string,
18 | canBubble?: boolean
19 | ): Event {
20 | return dispatchEvent(node, createFakeEvent(type, canBubble));
21 | }
22 |
23 | /** Shorthand to dispatch a keyboard event with a specified key code. */
24 | export function dispatchKeyboardEvent(
25 | node: Node,
26 | type: string,
27 | keyCode: number,
28 | target?: Element
29 | ): KeyboardEvent {
30 | return dispatchEvent(
31 | node,
32 | createKeyboardEvent(type, keyCode, target)
33 | ) as KeyboardEvent;
34 | }
35 |
36 | /** Shorthand to dispatch a mouse event on the specified coordinates. */
37 | export function dispatchMouseEvent(
38 | node: Node,
39 | type: string,
40 | x = 0,
41 | y = 0,
42 | event = createMouseEvent(type, x, y)
43 | ): MouseEvent {
44 | return dispatchEvent(node, event) as MouseEvent;
45 | }
46 |
47 | /** Shorthand to dispatch a touch event on the specified coordinates. */
48 | export function dispatchTouchEvent(node: Node, type: string, x = 0, y = 0) {
49 | return dispatchEvent(node, createTouchEvent(type, x, y));
50 | }
51 |
--------------------------------------------------------------------------------
/.docgenirc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@docgeni/core').DocgeniConfig}
3 | */
4 | module.exports = {
5 | mode: 'lite',
6 | title: 'NGX-VALIDATOR',
7 | logoUrl: 'https://cdn.pingcode.com/open-sources/angular/angular.svg',
8 | repoUrl: 'https://github.com/why520crazy/ngx-validator',
9 | description: '',
10 | docsDir: 'docs',
11 | navs: [
12 | null,
13 | {
14 | title: 'Components',
15 | path: 'components',
16 | lib: 'core',
17 | locales: {
18 | 'zh-cn': {
19 | title: '组件'
20 | }
21 | }
22 | },
23 | {
24 | title: 'GitHub',
25 | path: 'https://github.com/why520crazy/ngx-validator',
26 | isExternal: true
27 | },
28 | {
29 | title: 'CHANGELOG',
30 | path: 'https://github.com/why520crazy/ngx-validator/blob/master/CHANGELOG.md',
31 | isExternal: true,
32 | locales: {
33 | 'zh-cn': {
34 | title: '更新日志'
35 | }
36 | }
37 | }
38 | ],
39 | libs: [
40 | {
41 | name: 'core',
42 | rootDir: 'packages/core',
43 | abbrName: 'ngx',
44 | include: ['src', 'examples'],
45 | categories: []
46 | }
47 | ],
48 | locales: [
49 | {
50 | key: 'zh-cn',
51 | name: '中文'
52 | },
53 | {
54 | key: 'en-us',
55 | name: 'English'
56 | }
57 | ]
58 | };
59 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/custom-select/custom-select.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ContentChildren, QueryList, ElementRef, forwardRef } from '@angular/core';
2 | import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
3 |
4 | export const CUSTOM_SELECT_VALUE_ACCESSOR: any = {
5 | provide: NG_VALUE_ACCESSOR,
6 | useExisting: forwardRef(() => CustomSelectComponent),
7 | multi: true
8 | };
9 |
10 | const noop = () => {};
11 |
12 | @Component({
13 | selector: 'app-custom-select',
14 | templateUrl: './custom-select.component.html',
15 | styleUrls: ['./custom-select.component.scss'],
16 | providers: [CUSTOM_SELECT_VALUE_ACCESSOR]
17 | })
18 | export class CustomSelectComponent implements OnInit, ControlValueAccessor {
19 | selectedValue: string;
20 |
21 | private onTouchedCallback: () => void = noop;
22 |
23 | private onChangeCallback: (_: any) => void = noop;
24 |
25 | @ContentChildren('option') options: QueryList;
26 |
27 | constructor() {}
28 |
29 | ngOnInit() {}
30 |
31 | writeValue(obj: any): void {
32 | this.selectedValue = obj;
33 | }
34 |
35 | registerOnChange(fn: any): void {
36 | this.onChangeCallback = fn;
37 | }
38 |
39 | registerOnTouched(fn: any): void {
40 | this.onTouchedCallback = fn;
41 | }
42 |
43 | setDisabledState?(isDisabled: boolean): void {}
44 |
45 | selectOption(option: ElementRef) {
46 | this.selectedValue = option.nativeElement.value;
47 | this.onChangeCallback(option.nativeElement.value);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/core/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: { random: false },
17 | clearContext: false // leave Jasmine Spec Runner output visible in browser
18 | },
19 | jasmineHtmlReporter: {
20 | suppressAll: true // removes the duplicated traces
21 | },
22 | coverageReporter: {
23 | dir: require('path').join(__dirname, '../../coverage'),
24 | subdir: '.',
25 | reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'lcovonly' }]
26 | },
27 | angularCli: {
28 | environment: 'dev'
29 | },
30 | files: [],
31 | reporters: ['progress', 'kjhtml'],
32 | port: 9876,
33 | colors: true,
34 | logLevel: config.LOG_INFO,
35 | autoWatch: true,
36 | browsers: ['Chrome'],
37 | singleRun: false,
38 | customLaunchers: {
39 | ChromeHeadlessCI: {
40 | base: 'ChromeHeadless',
41 | flags: ['--no-sandbox']
42 | }
43 | },
44 | restartOnFileChange: true
45 | });
46 | };
47 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/template-driven.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, HostBinding } from '@angular/core';
2 | import { NgxValidatorConfig, NgxValidateOn } from '@why520crazy/ngx-validator';
3 | import { of } from 'rxjs';
4 | import { delay } from 'rxjs/operators';
5 |
6 | @Component({
7 | selector: 'ngx-validator-template-driven-example',
8 | templateUrl: './template-driven.component.html',
9 | styleUrls: ['./template-driven.component.scss']
10 | })
11 | export class NgxValidatorTemplateDrivenExampleComponent {
12 | message = '';
13 |
14 | showSex = false;
15 |
16 | validateOn: NgxValidateOn = 'change';
17 |
18 | loadingDone = true;
19 |
20 | model = {
21 | username: '',
22 | email: '',
23 | password: '',
24 | number: '',
25 | sex: '',
26 | customSelectValue: ''
27 | };
28 |
29 | validatorConfig: NgxValidatorConfig = {
30 | validationMessages: {
31 | username: {
32 | required: '用户名不能为空',
33 | pattern: '用户名格式不正确,以字母,数字,下划线组成,首字母不能为数字,必须是2-20个字符',
34 | ngxUniqueCheck: '输入的用户名已经存在,请重新输入'
35 | }
36 | },
37 | validateOn: this.validateOn
38 | };
39 |
40 | changeValidateOn() {
41 | this.loadingDone = false;
42 | this.validatorConfig.validateOn = this.validateOn;
43 | setTimeout(() => {
44 | this.loadingDone = true;
45 | }, 0);
46 | }
47 |
48 | checkUsername = (value: string) => {
49 | return value === 'peter' ? of(true).pipe(delay(200)) : of(false).pipe(delay(200));
50 | };
51 |
52 | setMessage(message: string) {
53 | this.message = message;
54 | }
55 |
56 | submit() {
57 | this.setMessage('This form has submit');
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/core/src/validators.spec.ts:
--------------------------------------------------------------------------------
1 | import { AbstractControl, FormControl, ValidationErrors } from '@angular/forms';
2 | import { of, Observable } from 'rxjs';
3 | import { NgxValidators } from './validators';
4 |
5 | describe('NgxValidators', () => {
6 | describe('ngxUniqueCheck', () => {
7 | let ngxUniqueCheck: (
8 | control: AbstractControl
9 | ) => Promise | Observable;
10 |
11 | const uniqueCheck = value => {
12 | return value === 'unique' ? of(false) : of(true);
13 | };
14 |
15 | const handleUniqueCheck = (payload: Promise | Observable) => {
16 | let errorMap: ValidationErrors;
17 | if (payload instanceof Observable) {
18 | payload.subscribe(data => {
19 | errorMap = data;
20 | });
21 | return errorMap;
22 | }
23 | };
24 |
25 | beforeEach(() => {
26 | ngxUniqueCheck = NgxValidators.uniqueCheckValidator(uniqueCheck);
27 | });
28 |
29 | it('should error on an empty string', () => {
30 | expect(handleUniqueCheck(ngxUniqueCheck(new FormControl('')))).toEqual(null);
31 | });
32 |
33 | it('should error on null', () => {
34 | expect(handleUniqueCheck(ngxUniqueCheck(new FormControl(null)))).toEqual(null);
35 | });
36 |
37 | it('should not error on undefined', () => {
38 | expect(handleUniqueCheck(ngxUniqueCheck(new FormControl(undefined)))).toEqual(null);
39 | });
40 |
41 | it('should valid on unique value', () => {
42 | expect(handleUniqueCheck(ngxUniqueCheck(new FormControl('unique')))).toEqual(null);
43 | });
44 |
45 | it('should invalid on a repeat value', () => {
46 | expect(handleUniqueCheck(ngxUniqueCheck(new FormControl('repeat'))).ngxUniqueCheck).toEqual({
47 | value: true
48 | });
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/packages/core/src/directives/validators.ts:
--------------------------------------------------------------------------------
1 | import { Directive, forwardRef, Input } from '@angular/core';
2 | import {
3 | NG_VALIDATORS,
4 | Validator,
5 | AbstractControl,
6 | Validators,
7 | ValidatorFn,
8 | NG_ASYNC_VALIDATORS,
9 | AsyncValidator,
10 | ValidationErrors
11 | } from '@angular/forms';
12 | import { Observable, of } from 'rxjs';
13 | import { NgxValidators } from '../validators';
14 |
15 | @Directive({
16 | selector: '[ngxMin][formControlName],[ngxMin][formControl],[ngxMin][ngModel],',
17 | providers: [
18 | {
19 | provide: NG_VALIDATORS,
20 | useExisting: forwardRef(() => MinValidatorDirective),
21 | multi: true
22 | }
23 | ]
24 | })
25 | export class MinValidatorDirective implements Validator {
26 | private validator: ValidatorFn;
27 |
28 | @Input() public set ngxMin(value: string) {
29 | this.validator = Validators.min(parseFloat(value));
30 | }
31 |
32 | constructor() {}
33 |
34 | validate(control: AbstractControl) {
35 | return this.validator(control);
36 | }
37 | }
38 |
39 | @Directive({
40 | selector: '[ngxMax][formControlName],[ngxMax][formControl],[ngxMax][ngModel]',
41 | providers: [
42 | {
43 | provide: NG_VALIDATORS,
44 | useExisting: forwardRef(() => MaxValidatorDirective),
45 | multi: true
46 | }
47 | ]
48 | })
49 | export class MaxValidatorDirective implements Validator {
50 | private validator: ValidatorFn;
51 |
52 | @Input() public set ngxMax(value: string) {
53 | this.validator = Validators.max(parseFloat(value));
54 | }
55 |
56 | constructor() {}
57 |
58 | validate(control: AbstractControl) {
59 | return this.validator(control);
60 | }
61 | }
62 |
63 | @Directive({
64 | selector: '[ngxUniqueCheck][formControlName],[ngxUniqueCheck][formControl],[ngxUniqueCheck][ngModel]',
65 | providers: [
66 | {
67 | provide: NG_ASYNC_VALIDATORS,
68 | useExisting: NgxUniqueCheckDirective,
69 | multi: true
70 | }
71 | ]
72 | })
73 | export class NgxUniqueCheckDirective implements AsyncValidator {
74 | @Input() ngxUniqueCheck: (value: any) => Observable = (value: any) => of(null);
75 |
76 | constructor() {}
77 |
78 | validate(control: AbstractControl): Promise | Observable {
79 | return NgxValidators.uniqueCheckValidator(this.ngxUniqueCheck)(control);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/packages/core/src/validator-loader.service.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable, Optional } from '@angular/core';
2 | import {
3 | NgxValidatorGlobalConfig,
4 | NgxValidationMessages,
5 | NGX_VALIDATOR_CONFIG,
6 | DEFAULT_GLOBAL_VALIDATION_MESSAGES
7 | } from './validator.class';
8 | import { ValidationFeedbackStrategy, ValidationFeedbackStrategyBuilder } from './strategies';
9 |
10 | const defaultValidatorConfig: NgxValidatorGlobalConfig = {
11 | validationFeedbackStrategy: ValidationFeedbackStrategyBuilder.bootstrap(),
12 | validationMessages: {}
13 | };
14 |
15 | @Injectable()
16 | export class NgxValidatorLoader {
17 | private config: NgxValidatorGlobalConfig;
18 |
19 | private getDefaultValidationMessage(key: string) {
20 | if (this.config.globalValidationMessages && this.config.globalValidationMessages[key]) {
21 | return this.config.globalValidationMessages[key];
22 | } else {
23 | return DEFAULT_GLOBAL_VALIDATION_MESSAGES[key];
24 | }
25 | }
26 |
27 | get validationMessages() {
28 | return this.config.validationMessages;
29 | }
30 |
31 | get validationFeedbackStrategy(): ValidationFeedbackStrategy {
32 | if (!this.config.validationFeedbackStrategy) {
33 | this.config.validationFeedbackStrategy = ValidationFeedbackStrategyBuilder.bootstrap();
34 | }
35 | return this.config.validationFeedbackStrategy;
36 | }
37 |
38 | get validateOn() {
39 | if (!this.config.validateOn) {
40 | this.config.validateOn = 'submit';
41 | }
42 | return this.config.validateOn;
43 | }
44 |
45 | constructor(
46 | @Optional()
47 | @Inject(NGX_VALIDATOR_CONFIG)
48 | config: NgxValidatorGlobalConfig
49 | ) {
50 | this.config = Object.assign({}, defaultValidatorConfig, config);
51 | }
52 |
53 | /**
54 | * get validation error messages
55 | * @param name formControl name, e.g. username or email
56 | * @param key validator name, e.g. required or pattern
57 | */
58 | getErrorMessage(name: string, key: string) {
59 | let message = '';
60 | if (this.validationMessages[name] && this.validationMessages[name][key]) {
61 | message = this.validationMessages[name][key];
62 | } else {
63 | message = this.getDefaultValidationMessage(key);
64 | }
65 | return message;
66 | }
67 |
68 | addValidationMessages(messages: NgxValidationMessages) {
69 | Object.assign(this.config.validationMessages, messages);
70 | }
71 |
72 | setGlobalValidationMessages(validationMessages: Record) {
73 | this.config.globalValidationMessages = validationMessages;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/reactive-driven/reactive-driven.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, HostBinding } from '@angular/core';
2 | import { NgxValidatorConfig, NgxValidators, NgxValidateOn } from '@why520crazy/ngx-validator';
3 | import { of } from 'rxjs';
4 | import { delay } from 'rxjs/operators';
5 | import { FormBuilder, Validators, FormGroup } from '@angular/forms';
6 |
7 | @Component({
8 | selector: 'ngx-validator-reactive-driven-example',
9 | templateUrl: './reactive-driven.component.html',
10 | styleUrls: ['./reactive-driven.component.scss']
11 | })
12 | export class NgxValidatorReactiveDrivenExampleComponent {
13 | message = '';
14 |
15 | formGroup: FormGroup;
16 |
17 | model = {
18 | username: '',
19 | email: '',
20 | password: '',
21 | number: ''
22 | };
23 |
24 | validateOn: NgxValidateOn = 'change';
25 |
26 | loadingDone = true;
27 |
28 | validatorConfig: NgxValidatorConfig = {
29 | validationMessages: {
30 | username: {
31 | required: '用户名不能为空',
32 | pattern: '用户名格式不正确,以字母,数字,下划线组成,首字母不能为数字,必须是2-20个字符',
33 | ngxUniqueCheck: '输入的用户名已经存在,请重新输入'
34 | },
35 | street: {
36 | required: 'street不能为空'
37 | }
38 | },
39 | validateOn: this.validateOn
40 | };
41 |
42 | constructor(private formBuilder: FormBuilder) {
43 | this.formGroup = this.formBuilder.group({
44 | email: ['', [Validators.required, Validators.email]],
45 | username: [
46 | '',
47 | [Validators.required, Validators.pattern('^[A-Za-z]{1}[0-9A-Za-z_]{1,19}')],
48 | NgxValidators.uniqueCheckValidator(this.checkUsername)
49 | ],
50 | password: ['', [Validators.required, Validators.maxLength(10), Validators.minLength(6)]],
51 | number: ['', [Validators.required, Validators.max(100), Validators.min(10)]],
52 | address: this.formBuilder.group({
53 | street: ['', Validators.required],
54 | city: this.formBuilder.group({
55 | country: ['', Validators.required]
56 | })
57 | })
58 | });
59 | }
60 |
61 | changeValidateOn() {
62 | this.loadingDone = false;
63 | this.validatorConfig.validateOn = this.validateOn;
64 | setTimeout(() => {
65 | this.loadingDone = true;
66 | });
67 | }
68 |
69 | checkUsername = (value: string) => {
70 | return value === 'peter' ? of(true).pipe(delay(200)) : of(false).pipe(delay(200));
71 | };
72 |
73 | setMessage(message: string) {
74 | this.message = message;
75 | }
76 |
77 | submit() {
78 | this.setMessage('This form has submit');
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/packages/core/src/validator-loader.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { NgxValidatorLoader } from './validator-loader.service';
4 | import { BootstrapValidationFeedbackStrategy } from './strategies';
5 | import { DEFAULT_GLOBAL_VALIDATION_MESSAGES } from './validator.class';
6 |
7 | describe('NgxValidatorLoader', () => {
8 | beforeEach(() =>
9 | TestBed.configureTestingModule({
10 | providers: [NgxValidatorLoader]
11 | })
12 | );
13 |
14 | it('should be created NgxValidatorLoader service', () => {
15 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
16 | expect(service).toBeTruthy();
17 | expect(service).not.toBeNull();
18 | expect(service).toBeDefined();
19 | });
20 |
21 | it(`should get empty validation messages`, () => {
22 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
23 | expect(service.validationMessages).toEqual({});
24 | });
25 |
26 | it(`should get bootstrap validation feedback strategy`, () => {
27 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
28 | expect(service.validationFeedbackStrategy instanceof BootstrapValidationFeedbackStrategy).toBe(true);
29 | });
30 |
31 | it(`should get default global message when formControl's message is not specified`, () => {
32 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
33 | ['required', 'email'].forEach(validatorName => {
34 | expect(service.getErrorMessage(`not_exist_form_control_name`, validatorName)).toBe(
35 | DEFAULT_GLOBAL_VALIDATION_MESSAGES[validatorName]
36 | );
37 | });
38 | });
39 |
40 | it(`should get configured global message when formControl's message is not specified`, () => {
41 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
42 | const globalValidationMessages = {
43 | required: 'this is configured required message'
44 | };
45 | service.setGlobalValidationMessages(globalValidationMessages);
46 | expect(service.getErrorMessage(`not_exist_form_control_name`, 'required')).toBe(
47 | globalValidationMessages.required
48 | );
49 | });
50 |
51 | it(`should get formControl's configured message`, () => {
52 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
53 | const validationMessages = {
54 | username: {
55 | required: `this is username configured required message`
56 | }
57 | };
58 | service.addValidationMessages(validationMessages);
59 | expect(service.getErrorMessage(`username`, 'required')).toBe(validationMessages.username.required);
60 | expect(service.getErrorMessage(`username`, 'email')).toBe(DEFAULT_GLOBAL_VALIDATION_MESSAGES.email);
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/reactive-driven/reactive-driven.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
97 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-validator",
3 | "version": "16.0.0",
4 | "description": "Angular7+ form validator, make error tips easy and automatic. don't need to manually write error tips templates.",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "npm run start:docs",
8 | "start:docs": "docgeni serve --port 4900",
9 | "build-docs": "docgeni serve --skip-site",
10 | "build": "ng build core --configuration production",
11 | "build:docs": "docgeni build --configuration production --base-href=/ngx-validator/",
12 | "pub-only": "npm publish ./dist/core --access=public",
13 | "pub": "wpm publish",
14 | "pull-latest": "git checkout master && git pull origin master",
15 | "release": "wpm release",
16 | "test": "ng test core",
17 | "test:demo": "ng test integration",
18 | "lint": "ng lint core",
19 | "lint:demo": "ng lint integration",
20 | "e2e": "ng e2e",
21 | "contributors:add": "all-contributors add",
22 | "contributors:generate": "all-contributors generate",
23 | "report-coverage": "cat ./coverage/lcov.info | coveralls"
24 | },
25 | "keywords": [
26 | "validator",
27 | "angular",
28 | "form",
29 | "validation",
30 | "validate"
31 | ],
32 | "private": false,
33 | "repository": "https://github.com/why520crazy/ngx-validator",
34 | "author": "why520crazy ",
35 | "license": "MIT",
36 | "dependencies": {
37 | "@angular/animations": "^17.0.6",
38 | "@angular/common": "^17.0.6",
39 | "@angular/compiler": "^17.0.6",
40 | "@angular/core": "^17.0.6",
41 | "@angular/forms": "^17.0.6",
42 | "@angular/platform-browser": "^17.0.6",
43 | "@angular/platform-browser-dynamic": "^17.0.6",
44 | "@angular/router": "^17.0.6",
45 | "bootstrap": "^4.2.1",
46 | "core-js": "^2.5.4",
47 | "lib": "^3.0.2",
48 | "rxjs": "~6.5.5",
49 | "tslib": "^2.0.0",
50 | "zone.js": "~0.14.2"
51 | },
52 | "devDependencies": {
53 | "@angular-devkit/build-angular": "^17.0.6",
54 | "@angular/cli": "^17.0.6",
55 | "@angular/compiler-cli": "^17.0.6",
56 | "@angular/language-service": "^17.0.6",
57 | "@commitlint/cli": "^7.5.2",
58 | "@commitlint/config-conventional": "^7.5.0",
59 | "@docgeni/cli": "^2.0.0",
60 | "@docgeni/template": "^2.0.0",
61 | "@types/jasmine": "~3.6.0",
62 | "@types/jasminewd2": "~2.0.3",
63 | "@types/node": "^12.11.1",
64 | "@worktile/pkg-manager": "0.1.0",
65 | "all-contributors-cli": "^6.6.0",
66 | "coveralls": "^3.0.9",
67 | "highlight.js": "^10.4.1",
68 | "husky": "^2.3.0",
69 | "jasmine-core": "~4.5.0",
70 | "jasmine-spec-reporter": "~5.0.0",
71 | "karma": "~6.4.0",
72 | "karma-chrome-launcher": "~3.1.0",
73 | "karma-coverage": "~2.2.0",
74 | "karma-jasmine": "~5.1.0",
75 | "karma-jasmine-html-reporter": "~2.0.0",
76 | "ng-packagr": "^17.0.2",
77 | "prettier": "^1.17.1",
78 | "pretty-quick": "^1.10.0",
79 | "protractor": "~7.0.0",
80 | "standard-version": "^8.0.1",
81 | "ts-node": "~7.0.0",
82 | "typescript": "~5.2.2"
83 | },
84 | "husky": {
85 | "hooks": {
86 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
87 | "pre-commit": "pretty-quick --staged"
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/packages/core/src/testing/events.ts:
--------------------------------------------------------------------------------
1 | export function createMouseEvent(type: string, x = 0, y = 0, button = 0) {
2 | const event = document.createEvent('MouseEvent');
3 |
4 | event.initMouseEvent(
5 | type,
6 | true /* canBubble */,
7 | false /* cancelable */,
8 | window /* view */,
9 | 0 /* detail */,
10 | x /* screenX */,
11 | y /* screenY */,
12 | x /* clientX */,
13 | y /* clientY */,
14 | false /* ctrlKey */,
15 | false /* altKey */,
16 | false /* shiftKey */,
17 | false /* metaKey */,
18 | button /* button */,
19 | null /* relatedTarget */
20 | );
21 |
22 | // `initMouseEvent` doesn't allow us to pass the `buttons` and
23 | // defaults it to 0 which looks like a fake event.
24 | Object.defineProperty(event, 'buttons', { get: () => 1 });
25 |
26 | return event;
27 | }
28 |
29 | /** Creates a browser TouchEvent with the specified pointer coordinates. */
30 | export function createTouchEvent(type: string, pageX = 0, pageY = 0) {
31 | // In favor of creating events that work for most of the browsers, the event is created
32 | // as a basic UI Event. The necessary details for the event will be set manually.
33 | const event = document.createEvent('UIEvent');
34 | const touchDetails = { pageX, pageY };
35 |
36 | event['initUIEvent'](type, true, true, window, 0);
37 |
38 | // Most of the browsers don't have a "initTouchEvent" method that can be used to define
39 | // the touch details.
40 | Object.defineProperties(event, {
41 | touches: { value: [touchDetails] },
42 | targetTouches: { value: [touchDetails] },
43 | changedTouches: { value: [touchDetails] }
44 | });
45 |
46 | return event;
47 | }
48 |
49 | /** Dispatches a keydown event from an element. */
50 | export function createKeyboardEvent(type: string, keyCode: number, target?: Element, key?: string) {
51 | const event = document.createEvent('KeyboardEvent') as any;
52 | const originalPreventDefault = event.preventDefault;
53 |
54 | // Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`.
55 | if (event.initKeyEvent) {
56 | event.initKeyEvent(type, true, true, window, 0, 0, 0, 0, 0, keyCode);
57 | } else {
58 | event.initKeyboardEvent(type, true, true, window, 0, key, 0, '', false);
59 | }
60 |
61 | // Webkit Browsers don't set the keyCode when calling the init function.
62 | // See related bug https://bugs.webkit.org/show_bug.cgi?id=16735
63 | Object.defineProperties(event, {
64 | keyCode: { get: () => keyCode },
65 | key: { get: () => key },
66 | target: { get: () => target }
67 | });
68 |
69 | // IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
70 | event.preventDefault = function() {
71 | Object.defineProperty(event, 'defaultPrevented', { get: () => true });
72 | return originalPreventDefault.apply(this, arguments);
73 | };
74 |
75 | return event;
76 | }
77 |
78 | /** Creates a fake event object with any desired event type. */
79 | export function createFakeEvent(type: string, canBubble = false, cancelable = true) {
80 | const event = document.createEvent('Event');
81 | event.initEvent(type, canBubble, cancelable);
82 | return event;
83 | }
84 |
--------------------------------------------------------------------------------
/packages/core/src/module.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { NgxValidatorModule } from './module';
4 | import { NgModule } from '@angular/core';
5 | import { NgxValidatorLoader } from './public-api';
6 | import { ValidationFeedbackStrategyBuilder, NoopValidationFeedbackStrategy } from './strategies';
7 |
8 | const GLOBAL_VALIDATION_MESSAGES = {
9 | required: 'This option cannot be empty',
10 | maxlength: 'The length of this option input cannot be greater than {requiredLength}',
11 | minlength: 'The length of this option input cannot be less than {requiredLength}',
12 | ngxUniqueCheck: 'The input value already exists, please re-enter',
13 | email: 'The format of the input message is incorrect',
14 | repeat: 'Inconsistent input twice',
15 | pattern: 'The option input format is incorrect',
16 | number: 'Must enter a number',
17 | url: 'The input URL format is incorrect',
18 | max: 'The input value of this option cannot be greater than {max}',
19 | min: 'The input value of this option cannot be less than {min}'
20 | };
21 |
22 | const VALIDATION_MESSAGES = {
23 | username: {
24 | required: `this is username configured required message`
25 | }
26 | };
27 |
28 | @NgModule({
29 | imports: [NgxValidatorModule]
30 | })
31 | class AppModuleDirectly {}
32 |
33 | @NgModule({
34 | imports: [
35 | NgxValidatorModule.forRoot({
36 | globalValidationMessages: GLOBAL_VALIDATION_MESSAGES,
37 | validateOn: 'blur',
38 | validationMessages: VALIDATION_MESSAGES,
39 | validationFeedbackStrategy: ValidationFeedbackStrategyBuilder.noop()
40 | })
41 | ]
42 | })
43 | class AppModuleWithConfig {}
44 |
45 | describe('NgxValidatorModule', () => {
46 | it('should be created directly import module', () => {
47 | TestBed.configureTestingModule({
48 | imports: [AppModuleDirectly]
49 | });
50 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
51 | expect(service).toBeTruthy();
52 | });
53 |
54 | it('should be created import module with default config ', () => {
55 | TestBed.configureTestingModule({
56 | imports: [AppModuleWithConfig]
57 | });
58 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
59 | expect(service).toBeTruthy();
60 | });
61 |
62 | it('should be overwrite default global validation messages ', () => {
63 | TestBed.configureTestingModule({
64 | imports: [AppModuleWithConfig]
65 | });
66 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
67 | expect(service['config'].globalValidationMessages).toEqual(GLOBAL_VALIDATION_MESSAGES);
68 | });
69 |
70 | it('should be overwrite default validation messages', () => {
71 | TestBed.configureTestingModule({
72 | imports: [AppModuleWithConfig]
73 | });
74 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
75 | expect(service['config'].validationMessages).toEqual(VALIDATION_MESSAGES);
76 | });
77 |
78 | it('should be overwrite default validation feedback strategy', () => {
79 | TestBed.configureTestingModule({
80 | imports: [AppModuleWithConfig]
81 | });
82 | const service: NgxValidatorLoader = TestBed.inject(NgxValidatorLoader);
83 | expect(service['config'].validationFeedbackStrategy instanceof NoopValidationFeedbackStrategy).toEqual(true);
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ngx-validator
2 |
3 | [![Coverage Status][coveralls-image]][coveralls-url]
4 | [![Build Status][build-status]](https://circleci.com/gh/why520crazy/ngx-validator)
5 | [](https://www.npmjs.com/@why520crazy/ngx-validator)
6 | 
7 | [](https://github.com/docgeni/docgeni)
8 | [](#contributors)
9 |
10 | [coveralls-image]: https://coveralls.io/repos/github/why520crazy/ngx-validator/badge.svg?branch=master
11 | [coveralls-url]: https://coveralls.io/github/why520crazy/ngx-validator
12 | [build-status]: https://circleci.com/gh/why520crazy/ngx-validator.svg?style=svg
13 |
14 | An Angular 7+ form validator library, may be the best angular validator library in the world.
15 |
16 | > handle validation messages easy and automatic, don't need to manually write error tips templates, just configure validation rules, and support extensive custom feedback strategy.
17 |
18 | ## Demo
19 |
20 | [Live Demo](https://why520crazy.github.io/ngx-validator)
21 |
22 | [Use Case](https://worktile.com/signup?utm_source=w5c-ngx-validator)
23 |
24 | ## Installation
25 |
26 | ```
27 | npm install @why520crazy/ngx-validator --save
28 | # or
29 | yarn add @why520crazy/ngx-validator
30 | ```
31 |
32 | ## Usage
33 |
34 | See https://why520crazy.github.io/ngx-validator/
35 | ## Documentation
36 |
37 | - [如何优雅的使用 Angular 表单验证](https://zhuanlan.zhihu.com/p/51467181)
38 | - [Angular 表单验证类库 ngx-validator 1.0 正式发布](https://github.com/why520crazy/ngx-validator/blob/master/1.0.0-publish.md)
39 |
40 | ## Development
41 |
42 | ```
43 | $ git clone git@github.com:why520crazy/ngx-validator.git
44 | $ cd ngx-validator
45 | $ npm install
46 | $ npm run start // http://127.0.0.1:4900
47 | $ npm run test
48 | ```
49 |
50 | ## Building & Publish
51 |
52 | ```
53 | $ npm run build
54 | $ npm run pub
55 | ```
56 |
57 | ## Links
58 |
59 | - [Angular.io](https://angular.io)
60 | - [Angular.cn](https://angular.cn)
61 | - [Worktile.com](https://worktile.com?utm_source=w5c-ngx-validator)
62 |
63 | ## License
64 |
65 | [MIT License](https://github.com/why520crazy/ngx-validator/blob/master/LICENSE)
66 |
67 | ## Contributors ✨
68 |
69 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
78 |
--------------------------------------------------------------------------------
/packages/core/examples/form-validator/examples/template-driven/template-driven.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
121 |
--------------------------------------------------------------------------------
/docs/zh-cn/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 介绍
3 | order: 10
4 | ---
5 | 一个 Angular 7+ 的表单验证类库,可能是世界上最好用的 Angular 表单验证类库
6 | > 自动处理验证消息,不需要手动写错误提示的模板,只需要配置验证规则即可,同时支持扩展自定义验证策略。
7 |
8 | ## 安装
9 |
10 | ```
11 | npm install @why520crazy/ngx-validator --save
12 | # or
13 | yarn add @why520crazy/ngx-validator
14 | ```
15 |
16 | ## 快速开始
17 |
18 |
19 | ### 导入 NgxValidatorModule
20 | 在主模块或者任何特性模块中导入 NgxValidatorModule 模块
21 | ```ts
22 | import { NgxValidatorModule, ValidationFeedbackStrategyBuilder } from '@why520crazy/ngx-validator';
23 |
24 | @NgModule({
25 | imports: [
26 | CommonModule,
27 | NgxValidatorModule.forRoot({
28 | validationFeedbackStrategy: ValidationFeedbackStrategyBuilder.bootstrap(), // default is bootstrap 4 style
29 | validationMessages: {
30 | username: {
31 | required: 'Username is required.',
32 | pattern: 'Incorrect username format.'
33 | }
34 | },
35 | validateOn: 'submit' | 'blur' // default is submit
36 | })
37 | ]
38 | })
39 | class AppModule {}
40 | ```
41 |
42 | ### 在表单上添加指令
43 |
44 | 在表单元素上添加 `ngxFormValidator` 指令并在提交按钮上添加 `ngxFormSubmit` 指令处理提交事件。
45 |
46 | ```html
47 |