├── .angular-cli.json
├── .codeclimate.yaml
├── .editorconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── .yo-rc.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE.md
├── LICENSE.txt
├── README.md
├── examples
└── angular4
│ ├── .angular-cli.json
│ ├── .editorconfig
│ ├── .gitignore
│ ├── README.md
│ ├── karma.conf.js
│ ├── package.json
│ ├── protractor.conf.js
│ ├── src
│ ├── app
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── core
│ │ │ ├── core.module.ts
│ │ │ ├── custom-asyncvalidators
│ │ │ │ └── is-blacklisted-name.validator.ts
│ │ │ └── custom-validators
│ │ │ │ └── name.validator.ts
│ │ └── page
│ │ │ └── basic-usage
│ │ │ ├── basic-usage.component.html
│ │ │ ├── basic-usage.component.scss
│ │ │ ├── basic-usage.component.ts
│ │ │ └── control-state
│ │ │ ├── control-state.component.html
│ │ │ └── control-state.component.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.scss
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── typings.d.ts
│ ├── tsconfig.json
│ └── tslint.json
├── gulpfile.js
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── resources
└── demo.gif
├── scripts
├── check-remote.ps1
├── create-changelog.ps1
├── detect-project-settings.ps1
├── github-release.ps1
├── install.ps1
├── release.ps1
├── setup-and-test.ps1
└── version-bump.ps1
├── src
├── alternative-validation.directive.spec.ts
├── alternative-validation.directive.ts
├── index.ts
├── main.ts
├── package.json
├── polyfills.ts
├── struct
│ ├── alternative-validation-config.ts
│ └── validator-config.ts
├── test.ts
├── tsconfig.es5.json
├── tsconfig.spec.json
├── validation-collector.service.ts
└── validation-collector.spec.ts
├── tools
└── gulp
│ └── inline-resources.js
├── tsconfig.json
└── tslint.json
/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "a-f-p"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "",
21 | "styles": [
22 | "styles.css"
23 | ],
24 | "scripts": [],
25 | "environmentSource": "environments/environment.ts",
26 | "environments": {
27 | "dev": "environments/environment.ts",
28 | "prod": "environments/environment.prod.ts"
29 | }
30 | }
31 | ],
32 | "e2e": {
33 | "protractor": {
34 | "config": "./protractor.conf.js"
35 | }
36 | },
37 | "lint": [
38 | {
39 | "project": "src/tsconfig.app.json"
40 | },
41 | {
42 | "project": "src/tsconfig.spec.json"
43 | },
44 | {
45 | "project": "e2e/tsconfig.e2e.json"
46 | }
47 | ],
48 | "test": {
49 | "karma": {
50 | "config": "./karma.conf.js"
51 | },
52 | "codeCoverage": {
53 | "exclude": ["./src/static-vendors/**/*"]
54 | }
55 | },
56 | "defaults": {
57 | "styleExt": "css",
58 | "component": {}
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.codeclimate.yaml:
--------------------------------------------------------------------------------
1 | engines:
2 | duplication:
3 | enabled: true
4 | config:
5 | languages:
6 | - javascript
7 | eslint:
8 | enabled: true
9 | fixme:
10 | enabled: true
11 | ratings:
12 | paths:
13 | - "**.css"
14 | - "**.ts"
15 | - "**.js"
16 | exclude_paths: [
17 | "examples/**/*",
18 | "./examples/**/*",
19 | "examples/",
20 | "./examples/"
21 | ]
22 |
23 | parserOptions:
24 | sourceType: module
25 |
26 | env:
27 | amd: true
28 | browser: true
29 | es6: true
30 |
31 | # http://eslint.org/docs/rules/
32 | rules:
33 | # Possible Errors
34 | no-await-in-loop: off
35 | no-cond-assign: error
36 | no-console: off
37 | no-constant-condition: error
38 | no-control-regex: error
39 | no-debugger: error
40 | no-dupe-args: error
41 | no-dupe-keys: error
42 | no-duplicate-case: error
43 | no-empty-character-class: error
44 | no-empty: error
45 | no-ex-assign: error
46 | no-extra-boolean-cast: error
47 | no-extra-parens: off
48 | no-extra-semi: error
49 | no-func-assign: error
50 | no-inner-declarations:
51 | - error
52 | - functions
53 | no-invalid-regexp: error
54 | no-irregular-whitespace: error
55 | no-negated-in-lhs: error
56 | no-obj-calls: error
57 | no-prototype-builtins: off
58 | no-regex-spaces: error
59 | no-sparse-arrays: error
60 | no-template-curly-in-string: off
61 | no-unexpected-multiline: error
62 | no-unreachable: error
63 | no-unsafe-finally: off
64 | no-unsafe-negation: off
65 | use-isnan: error
66 | valid-jsdoc: off
67 | valid-typeof: error
68 |
69 | # Best Practices
70 | accessor-pairs: error
71 | array-callback-return: off
72 | block-scoped-var: off
73 | class-methods-use-this: off
74 | complexity:
75 | - error
76 | - 6
77 | consistent-return: off
78 | curly: off
79 | default-case: off
80 | dot-location: off
81 | dot-notation: off
82 | eqeqeq: error
83 | guard-for-in: error
84 | no-alert: error
85 | no-caller: error
86 | no-case-declarations: error
87 | no-div-regex: error
88 | no-else-return: off
89 | no-empty-function: off
90 | no-empty-pattern: error
91 | no-eq-null: error
92 | no-eval: error
93 | no-extend-native: error
94 | no-extra-bind: error
95 | no-extra-label: off
96 | no-fallthrough: error
97 | no-floating-decimal: off
98 | no-global-assign: off
99 | no-implicit-coercion: off
100 | no-implied-eval: error
101 | no-invalid-this: off
102 | no-iterator: error
103 | no-labels:
104 | - error
105 | - allowLoop: true
106 | allowSwitch: true
107 | no-lone-blocks: error
108 | no-loop-func: error
109 | no-magic-number: off
110 | no-multi-spaces: off
111 | no-multi-str: off
112 | no-native-reassign: error
113 | no-new-func: error
114 | no-new-wrappers: error
115 | no-new: error
116 | no-octal-escape: error
117 | no-octal: error
118 | no-param-reassign: off
119 | no-proto: error
120 | no-redeclare: error
121 | no-restricted-properties: off
122 | no-return-assign: error
123 | no-return-await: off
124 | no-script-url: error
125 | no-self-assign: off
126 | no-self-compare: error
127 | no-sequences: off
128 | no-throw-literal: off
129 | no-unmodified-loop-condition: off
130 | no-unused-expressions: error
131 | no-unused-labels: off
132 | no-useless-call: error
133 | no-useless-concat: error
134 | no-useless-escape: off
135 | no-useless-return: off
136 | no-void: error
137 | no-warning-comments: off
138 | no-with: error
139 | prefer-promise-reject-errors: off
140 | radix: error
141 | require-await: off
142 | vars-on-top: off
143 | wrap-iife: error
144 | yoda: off
145 |
146 | # Strict
147 | strict: off
148 |
149 | # Variables
150 | init-declarations: off
151 | no-catch-shadow: error
152 | no-delete-var: error
153 | no-label-var: error
154 | no-restricted-globals: off
155 | no-shadow-restricted-names: error
156 | no-shadow: off
157 | no-undef-init: error
158 | no-undef: off
159 | no-undefined: off
160 | no-unused-vars: off
161 | no-use-before-define: off
162 |
163 | # Node.js and CommonJS
164 | callback-return: error
165 | global-require: error
166 | handle-callback-err: error
167 | no-mixed-requires: off
168 | no-new-require: off
169 | no-path-concat: error
170 | no-process-env: off
171 | no-process-exit: error
172 | no-restricted-modules: off
173 | no-sync: off
174 |
175 | # Stylistic Issues
176 | array-bracket-spacing: off
177 | block-spacing: off
178 | brace-style: off
179 | camelcase: off
180 | capitalized-comments: off
181 | comma-dangle:
182 | - error
183 | - never
184 | comma-spacing: off
185 | comma-style: off
186 | computed-property-spacing: off
187 | consistent-this: off
188 | eol-last: off
189 | func-call-spacing: off
190 | func-name-matching: off
191 | func-names: off
192 | func-style: off
193 | id-length: off
194 | id-match: off
195 | indent: off
196 | jsx-quotes: off
197 | key-spacing: off
198 | keyword-spacing: off
199 | line-comment-position: off
200 | linebreak-style: off
201 | lines-around-comment: off
202 | lines-around-directive: off
203 | max-depth: off
204 | max-len: off
205 | max-nested-callbacks: off
206 | max-params: off
207 | max-statements-per-line: off
208 | max-statements:
209 | - error
210 | - 30
211 | multiline-ternary: off
212 | new-cap: off
213 | new-parens: off
214 | newline-after-var: off
215 | newline-before-return: off
216 | newline-per-chained-call: off
217 | no-array-constructor: off
218 | no-bitwise: off
219 | no-continue: off
220 | no-inline-comments: off
221 | no-lonely-if: off
222 | no-mixed-operators: off
223 | no-mixed-spaces-and-tabs: off
224 | no-multi-assign: off
225 | no-multiple-empty-lines: off
226 | no-negated-condition: off
227 | no-nested-ternary: off
228 | no-new-object: off
229 | no-plusplus: off
230 | no-restricted-syntax: off
231 | no-spaced-func: off
232 | no-tabs: off
233 | no-ternary: off
234 | no-trailing-spaces: off
235 | no-underscore-dangle: off
236 | no-unneeded-ternary: off
237 | object-curly-newline: off
238 | object-curly-spacing: off
239 | object-property-newline: off
240 | one-var-declaration-per-line: off
241 | one-var: off
242 | operator-assignment: off
243 | operator-linebreak: off
244 | padded-blocks: off
245 | quote-props: off
246 | quotes: off
247 | require-jsdoc: off
248 | semi-spacing: off
249 | semi: off
250 | sort-keys: off
251 | sort-vars: off
252 | space-before-blocks: off
253 | space-before-function-paren: off
254 | space-in-parens: off
255 | space-infix-ops: off
256 | space-unary-ops: off
257 | spaced-comment: off
258 | template-tag-spacing: off
259 | unicode-bom: off
260 | wrap-regex: off
261 |
262 | # ECMAScript 6
263 | arrow-body-style: off
264 | arrow-parens: off
265 | arrow-spacing: off
266 | constructor-super: off
267 | generator-star-spacing: off
268 | no-class-assign: off
269 | no-confusing-arrow: off
270 | no-const-assign: off
271 | no-dupe-class-members: off
272 | no-duplicate-imports: off
273 | no-new-symbol: off
274 | no-restricted-imports: off
275 | no-this-before-super: off
276 | no-useless-computed-key: off
277 | no-useless-constructor: off
278 | no-useless-rename: off
279 | no-var: off
280 | object-shorthand: off
281 | prefer-arrow-callback: off
282 | prefer-const: off
283 | prefer-destructuring: off
284 | prefer-numeric-literals: off
285 | prefer-rest-params: off
286 | prefer-reflect: off
287 | prefer-spread: off
288 | prefer-template: off
289 | require-yield: off
290 | rest-spread-spacing: off
291 | sort-imports: off
292 | symbol-description: off
293 | template-curly-spacing: off
294 | yield-star-spacing: off
295 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://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 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 |
5 | # TypeScript
6 | src/*.js
7 | src/*.map
8 | src/*.d.ts
9 |
10 | # JetBrains
11 | .idea
12 | .project
13 | .settings
14 | .idea/*
15 | *.iml
16 |
17 | # VS Code
18 | .vscode/*
19 |
20 | # Windows
21 | Thumbs.db
22 | Desktop.ini
23 |
24 | # Mac
25 | .DS_Store
26 | **/.DS_Store
27 |
28 | # Ngc generated files
29 | **/*.ngfactory.ts
30 |
31 | # Build files
32 | dist/*
33 |
34 | # Karma transform
35 | init-test-bed.spec.js
36 | src/**/*.js
37 |
38 | # coverage
39 | coverage
40 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 | docs/*
5 | # DO NOT IGNORE TYPESCRIPT FILES FOR NPM
6 | # TypeScript
7 | # *.js
8 | # *.map
9 | # *.d.ts
10 |
11 | # JetBrains
12 | .idea
13 | .project
14 | .settings
15 | .idea/*
16 | *.iml
17 |
18 | # VS Code
19 | .vscode/*
20 |
21 | # Windows
22 | Thumbs.db
23 | Desktop.ini
24 |
25 | # Mac
26 | .DS_Store
27 | **/.DS_Store
28 |
29 | # Ngc generated files
30 | **/*.ngfactory.ts
31 |
32 | # Library files
33 | src/*
34 | build/*
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | node_js:
4 | - '6.11.1'
5 |
6 | cache:
7 | directories:
8 | - ./node_modules
9 |
10 | install:
11 | - npm i
12 |
13 | before_script:
14 |
15 | script:
16 | - npm run coverage
17 |
18 | after_success:
19 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
20 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js
21 | - rm -rf ./coverage
22 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-angular2-library": {
3 | "promptValues": {
4 | "gitRepositoryUrl": "https://github.com/username/repo"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/1.0.5...0.3.6) (2017-08-08)
3 |
4 |
5 |
6 |
7 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/1.0.4...0.3.6) (2017-08-07)
8 |
9 |
10 |
11 |
12 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/1.0.3...0.3.6) (2017-08-07)
13 |
14 |
15 | ### Bug Fixes
16 |
17 | * **cleanup code:** cleanup for imports, params, and formatting ([5027f80](https://github.com/BioPhoton/angular-alternative-validation/commit/5027f80))
18 | * **linting:** changed jslint.json, fixed new linting errors ([cf3ce28](https://github.com/BioPhoton/angular-alternative-validation/commit/cf3ce28))
19 |
20 |
21 |
22 |
23 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/1.0.2...0.3.6) (2017-08-05)
24 |
25 |
26 |
27 |
28 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/1.0.1...0.3.6) (2017-08-05)
29 |
30 |
31 |
32 |
33 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/1.0.0...0.3.6) (2017-08-05)
34 |
35 |
36 |
37 |
38 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/0.3.10...0.3.6) (2017-08-05)
39 |
40 |
41 | ### Bug Fixes
42 |
43 | * **disabled state:** implement disabled state and observable ([cf31e75](https://github.com/BioPhoton/angular-alternative-validation/commit/cf31e75))
44 |
45 |
46 | ### Features
47 |
48 | * **first feature complete state:** first state i consider feature completeness ([cda0553](https://github.com/BioPhoton/angular-alternative-validation/commit/cda0553))
49 |
50 |
51 | ### BREAKING CHANGES
52 |
53 | * **first feature complete state:** -)
54 |
55 |
56 |
57 |
58 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/0.3.9...0.3.6) (2017-08-04)
59 |
60 |
61 | ### Bug Fixes
62 |
63 | * **implement AbstractControlDirective:** implemented AbstractControlDirective and updated example ([44e711c](https://github.com/BioPhoton/angular-alternative-validation/commit/44e711c))
64 |
65 |
66 |
67 |
68 | ## [0.3.6](https://github.com/BioPhoton/angular-alternative-validation/compare/0.3.9...0.3.6) (2017-08-04)
69 |
70 |
71 | ### Bug Fixes
72 |
73 | * **implement AbstractControlDirective:** implemented AbstractControlDirective and updated example ([44e711c](https://github.com/BioPhoton/angular-alternative-validation/commit/44e711c))
74 |
75 |
76 |
77 |
78 | ## [0.3.8](https://github.com/BioPhoton/angular-alternative-validation/compare/0.3.7...0.3.8) (2017-07-31)
79 |
80 |
81 |
82 |
83 | ## [0.3.7](https://github.com/BioPhoton/angular-alternative-validation/compare/434b965...0.3.7) (2017-07-31)
84 |
85 |
86 | ### Bug Fixes
87 |
88 | * **changelog:** implement changelog file and autometed building ([434b965](https://github.com/BioPhoton/angular-alternative-validation/commit/434b965)), closes [#7](https://github.com/BioPhoton/angular-alternative-validation/issues/7)
89 | * **example:** setup basic example ([57b302f](https://github.com/BioPhoton/angular-alternative-validation/commit/57b302f)), closes [#5](https://github.com/BioPhoton/angular-alternative-validation/issues/5)
90 | * **test-coverage:** setup tests coverage on coveralls and codecov ([50ecd2b](https://github.com/BioPhoton/angular-alternative-validation/commit/50ecd2b))
91 | * **unit-test:** setup unit-tests on local and on travis ([67d84e6](https://github.com/BioPhoton/angular-alternative-validation/commit/67d84e6))
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at michael@hladky.at. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## I'm submitting a...
2 |
3 |
4 | [ ] Bug report
5 | [ ] Feature request
6 |
7 |
8 | ## Current behavior
9 |
10 |
11 |
12 | ## Expected behavior
13 |
14 |
15 |
16 | ## Minimal reproduction of the problem with instructions
17 |
20 |
21 |
22 | ## Environment
23 |
24 |
25 | Library version: X.Y.Z
26 | Angular version: X.Y.Z
27 |
28 |
29 | Browser:
30 |
31 | - [ ] Chrome (desktop) version XX
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Michael Hladky
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-alternative-validation
2 |
3 | #### Angular Alternative Validation - The smoothest way to implement validation hints/warnings for your forms
4 |
5 | 
6 | [](https://www.npmjs.com/package/angular-alternative-validation)
7 | [](https://travis-ci.org/BioPhoton/angular-alternative-validation)
8 | [](https://coveralls.io/github/BioPhoton/angular-alternative-validation?branch=master)
9 |
10 | ## Demo
11 |
12 | - [x] [angular4 demo with ng-cli](https://github.com/BioPhoton/angular-alternative-validation/tree/master/examples/angular4)
13 | - [x] [plunkr demo](https://embed.plnkr.co/e3GOAFENPumfy78IWXAw/)
14 |
15 |
16 | 
17 |
18 | ## Quick code example:
19 | ``` typescript
20 | // app.component.ts
21 | ...
22 | import { IAlternativeValidationConfig } from 'angular-alternative-validation/struct/alternative-validation-config';
23 |
24 | @Component({
25 | selector: 'app-basic-usage',
26 | template: `
30 |
31 | {{fg.get('name').valid}} vs {{aV.valid}}`
32 | })
33 | export class BasicUsageComponent {
34 | ...
35 | }
36 |
37 | ```
38 |
39 |
40 | ## Basic Usage:
41 |
42 | #### Implement Library
43 |
44 | ``` bash
45 | $ npm install angular-alternative-validation --save
46 | ```
47 |
48 | ``` typescript
49 | // app.module.ts
50 | ...
51 | // IMPORT YOUR LIBRARY
52 | import { AlternativeValidationModule } from 'angular-alternative-validation';
53 |
54 | @NgModule({
55 | imports: [
56 | ...
57 | AlternativeValidationModule.forRoot();
58 | ]
59 | ...
60 | })
61 | export class AppModule { }
62 |
63 | ```
64 |
65 | #### Create alternative validation config object
66 |
67 | ``` typescript
68 | // app.component.ts
69 | ...
70 | import { IAlternativeValidationConfig } from 'angular-alternative-validation/struct/alternative-validation-config';
71 |
72 | @Component({
73 | selector: 'app-basic-usage',
74 | template: `
75 |
79 | `
80 | })
81 | export class BasicUsageComponent {
82 |
83 | aVConfig: IAlternativeValidationConfig = {
84 | validator: [
85 | {name: 'required'},
86 | {name: 'minLength', params: [3] }
87 | ]
88 | }
89 |
90 | formGroup: FormGroup;
91 |
92 | constructor(private fb: FormBuilder) {
93 | this.basicFormGroup = this.fb.group({ name: [] });
94 | }
95 |
96 | }
97 |
98 | ```
99 |
100 | #### Template reference to the directive
101 |
102 | ``` html
103 | // app.component.html
104 | ...
105 |
106 | {{aV.errors | json}} {{aV.valid}}
107 | ```
108 |
109 | #### A Reference to the directive in the class
110 |
111 | ``` typescript
112 | // app.component.ts
113 | ...
114 | @ViewChild(AlternativeValidationDirective)
115 | alternativeValidationRef
116 | ...
117 | ngAfterViewInit() {
118 | console.log('Directive referenc: ', this.alternativeValidationRef);
119 | }
120 | ...
121 | ```
122 |
123 | ## Use custom validations
124 |
125 | #### Create custom function
126 |
127 | ``` typescript
128 | // app.module.ts
129 | export function myValidation(param1, param2): ValidatorFn {
130 |
131 | }
132 |
133 | ...
134 | @NgModule({
135 | ...
136 | providers: [
137 | { provide: NG_VALIDATORS, useValue: myValidation, multi: true }
138 | ]
139 | ...
140 | })
141 | export class AppModule {
142 |
143 | }
144 |
145 | ```
146 |
147 | #### Use custom transform function in config object
148 |
149 | ``` typescript
150 | // app.component.ts
151 | ...
152 | export class BasicUsageComponent {
153 |
154 | fPConfig: IAlternativeValidationConfig = {
155 | alternativeValidation:[
156 | { name: 'myValidation', params: [param1, param2] }
157 | ]
158 | }
159 |
160 | }
161 |
162 | ```
163 |
164 | # What it is
165 |
166 | There are many ways to build a alternative validation state.
167 | Many of them can't reuse existing validators and all of them do not provide a separate state of validation.
168 |
169 | What this library do is it provides an alternative state of the host control.
170 | You can use it like the normal form control validation
171 | but it is not effecting the actual validation of the form.
172 |
173 | It's a mix of `FormControlName`, `AbstractControlDirective`, `ControlValueAccessor`, `NG_VALIDATORS` and a little bit of `magic-glue$`.
174 |
175 | In this way you can:
176 | - reuse default validators and async validators
177 | - treat the alternative control stated independent from the real one that effects formGroup and formControl states
178 | - display user hints/information separated from the error messages
179 | - use other libraries working with formControl logic
180 |
181 | # License
182 |
183 | MIT © [Michael Hladky](mailto:michael@hladky.at)
184 |
185 | Copyright 2017 [Michael Hladky](mailto:michael@hladky.at). All Rights Reserved.
186 | Use of this source code is governed by an MIT-style license that
187 | can be found in the LICENSE file at [angular-alternative-validation](https://github.com/BioPhoton/angular-alternative-validation/blob/master/LICENSE.txt)
188 |
--------------------------------------------------------------------------------
/examples/angular4/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "test-angular-alternative-validation"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "app",
21 | "styles": [
22 | "styles.scss"
23 | ],
24 | "scripts": [],
25 | "environmentSource": "environments/environment.ts",
26 | "environments": {
27 | "dev": "environments/environment.ts",
28 | "prod": "environments/environment.prod.ts"
29 | }
30 | }
31 | ],
32 | "e2e": {
33 | "protractor": {
34 | "config": "./protractor.conf.js"
35 | }
36 | },
37 | "lint": [
38 | {
39 | "project": "src/tsconfig.app.json"
40 | },
41 | {
42 | "project": "src/tsconfig.spec.json"
43 | },
44 | {
45 | "project": "e2e/tsconfig.e2e.json"
46 | }
47 | ],
48 | "test": {
49 | "karma": {
50 | "config": "./karma.conf.js"
51 | }
52 | },
53 | "defaults": {
54 | "styleExt": "scss",
55 | "component": {
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/angular4/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://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 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/examples/angular4/.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 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | testem.log
34 | /typings
35 |
36 | # e2e
37 | /e2e/*.js
38 | /e2e/*.map
39 |
40 | # System Files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/examples/angular4/README.md:
--------------------------------------------------------------------------------
1 | # TestAngularFormatterParser
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.1.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24 | Before running the tests make sure you are serving the app via `ng serve`.
25 |
26 | ## Further help
27 |
28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
29 |
--------------------------------------------------------------------------------
/examples/angular4/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 | angularCli: {
23 | environment: 'dev'
24 | },
25 | reporters: ['progress', 'kjhtml'],
26 | port: 9876,
27 | colors: true,
28 | logLevel: config.LOG_INFO,
29 | autoWatch: true,
30 | browsers: ['Chrome'],
31 | singleRun: false
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/examples/angular4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-angular-alternative-validation",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/animations": "^4.0.0",
16 | "@angular/common": "^4.0.0",
17 | "@angular/compiler": "^4.0.0",
18 | "@angular/core": "^4.0.0",
19 | "@angular/forms": "^4.0.0",
20 | "@angular/http": "^4.0.0",
21 | "@angular/platform-browser": "^4.0.0",
22 | "@angular/platform-browser-dynamic": "^4.0.0",
23 | "@angular/router": "^4.0.0",
24 | "angular-alternative-validation": "^0.2.0",
25 | "bootstrap": "^4.0.0-alpha.6",
26 | "core-js": "^2.4.1",
27 | "font-awesome": "^4.7.0",
28 | "rxjs": "^5.1.0",
29 | "zone.js": "^0.8.4"
30 | },
31 | "devDependencies": {
32 | "@angular/cli": "1.1.0",
33 | "@angular/compiler-cli": "^4.0.0",
34 | "@angular/language-service": "^4.0.0",
35 | "@types/jasmine": "2.5.45",
36 | "@types/node": "~6.0.60",
37 | "codelyzer": "~3.0.1",
38 | "jasmine-core": "~2.6.2",
39 | "jasmine-spec-reporter": "~4.1.0",
40 | "karma": "~1.7.0",
41 | "karma-chrome-launcher": "~2.1.1",
42 | "karma-cli": "~1.0.1",
43 | "karma-jasmine": "~1.1.0",
44 | "karma-jasmine-html-reporter": "^0.2.2",
45 | "karma-coverage-istanbul-reporter": "^1.2.1",
46 | "protractor": "~5.1.2",
47 | "ts-node": "~3.0.4",
48 | "tslint": "~5.3.2",
49 | "typescript": "~2.3.3"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/angular4/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { BasicUsageComponent } from './page/basic-usage/basic-usage.component';
4 |
5 | const routes: Routes = [
6 | {
7 | path: '',
8 | redirectTo: 'basic-usage',
9 | pathMatch: 'full'
10 | },
11 | {
12 | path: 'basic-usage',
13 | component: BasicUsageComponent
14 | },
15 | {
16 | path: '**',
17 | redirectTo: 'basic-usage',
18 | pathMatch: 'full'
19 | }
20 | ];
21 |
22 | @NgModule({
23 | imports: [RouterModule.forRoot(routes)],
24 | exports: [RouterModule]
25 | })
26 | export class AppRoutingModule {
27 | }
28 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioPhoton/angular-alternative-validation/a72f551819bc716c870ce086c7f6c404be244384/examples/angular4/src/app/app.component.scss
--------------------------------------------------------------------------------
/examples/angular4/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 |
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async(() => {
7 | TestBed.configureTestingModule({
8 | declarations: [
9 | AppComponent
10 | ],
11 | }).compileComponents();
12 | }));
13 |
14 | it('should create the app', async(() => {
15 | const fixture = TestBed.createComponent(AppComponent);
16 | const app = fixture.debugElement.componentInstance;
17 | expect(app).toBeTruthy();
18 | }));
19 |
20 | it(`should have as title 'app'`, async(() => {
21 | const fixture = TestBed.createComponent(AppComponent);
22 | const app = fixture.debugElement.componentInstance;
23 | expect(app.title).toEqual('app');
24 | }));
25 |
26 | it('should render title in a h1 tag', async(() => {
27 | const fixture = TestBed.createComponent(AppComponent);
28 | fixture.detectChanges();
29 | const compiled = fixture.debugElement.nativeElement;
30 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!');
31 | }));
32 | });
33 |
--------------------------------------------------------------------------------
/examples/angular4/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 |
10 | constructor() {
11 |
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core'
2 | import {ReactiveFormsModule} from '@angular/forms'
3 | import {BrowserModule} from '@angular/platform-browser'
4 | import {AppRoutingModule} from './app-routing.module'
5 |
6 | // Import your library
7 | import {AlternativeValidationModule} from 'angular-alternative-validation'
8 |
9 | // Project specific imports
10 | import {CoreModule} from './core/core.module'
11 | import {AppComponent} from './app.component'
12 | import {BasicUsageComponent} from './page/basic-usage/basic-usage.component'
13 | import {ControlStateComponent} from './page/basic-usage/control-state/control-state.component'
14 | @NgModule({
15 | declarations: [
16 | AppComponent,
17 | BasicUsageComponent,
18 | ControlStateComponent
19 | ],
20 | imports: [
21 | BrowserModule,
22 | AppRoutingModule,
23 | ReactiveFormsModule,
24 | AlternativeValidationModule.forRoot(),
25 | CoreModule
26 | ],
27 | bootstrap: [AppComponent]
28 | })
29 | export class AppModule {
30 | }
31 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule, Optional, SkipSelf } from '@angular/core';
3 | import {validName} from './custom-validators/name.validator'
4 | import {NG_VALIDATORS} from '@angular/forms'
5 |
6 | @NgModule({
7 | imports: [
8 | CommonModule
9 | ],
10 | exports: [
11 | CommonModule
12 | ],
13 | declarations: [],
14 | providers: [
15 | {provide: NG_VALIDATORS, useValue: validName, multi: true}
16 | ]
17 | })
18 | export class CoreModule {
19 | constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
20 | console.log('CoreModlue: ', parentModule);
21 | if (parentModule) {
22 | throw new Error(
23 | 'CoreModule is already loaded. Import it in the AppModule only');
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/core/custom-asyncvalidators/is-blacklisted-name.validator.ts:
--------------------------------------------------------------------------------
1 | import {AbstractControl, ValidationErrors} from '@angular/forms'
2 |
3 | import {Observable} from 'rxjs/Observable'
4 | import 'rxjs/add/operator/debounceTime'
5 | import 'rxjs/add/operator/distinctUntilChanged'
6 | import 'rxjs/add/operator/delay'
7 | import 'rxjs/add/operator/first'
8 |
9 | export function isBlacklistedName(c: AbstractControl): Observable {
10 |
11 | const validatorName = 'isBlacklistedName';
12 |
13 | const routValidation$ = new Observable(observer => {
14 |
15 | if (c.value && typeof c.value === 'string' &&
16 | 'abcde'.indexOf(c.value.toString().toLowerCase()) !== -1) {
17 | observer.next({
18 | [this.validationName]: {
19 | actual: c.value,
20 | mandatoryChars: 'abcde'
21 | }
22 | });
23 | } else {
24 | observer.next(null);
25 | }
26 | });
27 |
28 | return routValidation$.debounceTime(500).distinctUntilChanged().delay(2000).first();
29 |
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/core/custom-validators/name.validator.ts:
--------------------------------------------------------------------------------
1 | import {AbstractControl, ValidationErrors} from '@angular/forms'
2 |
3 | export function validName(c: AbstractControl): ValidationErrors | null {
4 | const validNames = ['Aretha', 'Ella', 'Etta', 'Nina'];
5 |
6 | const isValid = validNames
7 | .map(n => c.value && c.value.indexOf(n) !== -1)
8 | .filter(v => v)
9 | .reduce((prev, curr) => true, false);
10 |
11 | if (!isValid) {
12 | return {
13 | validName: {
14 | valid: true,
15 | validNames
16 | }
17 | }
18 | }
19 |
20 | return null
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/page/basic-usage/basic-usage.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | Angular control states
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | Alternative control states
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/page/basic-usage/basic-usage.component.scss:
--------------------------------------------------------------------------------
1 | ul {
2 | padding: 0;
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/page/basic-usage/basic-usage.component.ts:
--------------------------------------------------------------------------------
1 | import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core'
2 | import {
3 | AbstractControl,
4 | FormBuilder,
5 | FormControl,
6 | FormGroup,
7 | Validators
8 | } from '@angular/forms'
9 | import {AlternativeValidationDirective} from 'angular-alternative-validation'
10 | import {IAlternativeValidationConfig} from 'angular-alternative-validation/struct/alternative-validation-config'
11 |
12 | @Component({
13 | selector: 'basic-usage',
14 | templateUrl: './basic-usage.component.html',
15 | styleUrls: ['./basic-usage.component.scss']
16 | })
17 | export class BasicUsageComponent implements OnInit, AfterViewInit {
18 |
19 |
20 | basicFormGroup: FormGroup;
21 | altInput: FormControl;
22 | avNameConfig: IAlternativeValidationConfig;
23 |
24 | @ViewChild(AlternativeValidationDirective)
25 | ref
26 |
27 | constructor(private fb: FormBuilder) {
28 | this.basicFormGroup = this.fb.group(
29 | {
30 | alt: ['initial', [Validators.required, Validators.minLength(3)]]
31 | }
32 | );
33 |
34 | this.avNameConfig = {
35 | validator: [
36 | {name: 'validName'}
37 | ]
38 | };
39 | }
40 |
41 | ngOnInit() {
42 | this.altInput = this.basicFormGroup.get('alt') as FormControl
43 | }
44 |
45 | ngAfterViewInit() {
46 | console.log('Reference to the directive', this.ref);
47 | }
48 |
49 | getControlFeedbackName(formControl: AbstractControl, altControl: AbstractControl): string {
50 | if (formControl.disabled) {
51 | return '';
52 | }
53 | if (formControl.invalid) {
54 | return 'danger';
55 | } else if (altControl.invalid) {
56 | return 'warning';
57 | } else {
58 | return 'success';
59 | }
60 | }
61 |
62 | toggleDisabled(control: AbstractControl) {
63 | if (!control.disabled) {
64 | control.disable();
65 | } else {
66 | control.enable();
67 | }
68 | }
69 |
70 | resetWithValue(value) {
71 | this.basicFormGroup.reset({
72 | alt: 'reset'
73 | })
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/page/basic-usage/control-state/control-state.component.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
10 | -
11 | {{error.name}}
12 |
13 |
14 |
15 |
16 |
17 |
value
18 |
20 | {{control.value}}
21 |
22 |
23 |
valid
24 |
26 | {{control.valid }}
27 |
28 |
29 |
invalid
30 |
32 | {{control.invalid }}
33 |
34 |
35 |
status
36 |
{{control.status}}
41 |
42 |
43 |
pending
44 |
46 | {{control.pending}}
47 |
48 |
49 |
pristine
50 |
52 | {{control.pristine}}
53 |
54 |
dirty
55 |
57 | {{control.dirty}}
58 |
59 |
60 |
touched
61 |
63 | {{control.touched}}
64 |
65 |
66 |
untouched
67 |
69 | {{control.untouched}}
70 |
71 |
72 |
focus
73 |
75 | {{control.focus}}
76 |
77 |
78 |
disabled
79 |
81 | {{control.disabled}}
82 |
83 |
84 |
errors
85 |
87 |
{{control.errors | json}}
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/examples/angular4/src/app/page/basic-usage/control-state/control-state.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core'
2 | import {FormControl} from '@angular/forms'
3 |
4 | @Component({
5 | selector: 'control-state',
6 | templateUrl: './control-state.component.html',
7 | styles: [`
8 | ul {
9 | list-style: none;
10 | margin-bottom:0;
11 | }
12 | `]
13 | })
14 | export class ControlStateComponent implements OnChanges, OnInit {
15 |
16 | @Input()
17 | control: FormControl;
18 |
19 | @Input()
20 | errorColor = 'danger';
21 |
22 | ngOnChanges(changes: SimpleChanges): void {
23 |
24 | console.log(changes);
25 |
26 | if ('errorColor' in changes) {
27 | this.errorColor = changes.errorColor.currentValue || 'danger'
28 | }
29 | }
30 |
31 | ngOnInit() {
32 |
33 | }
34 |
35 | getErrors(): any[] {
36 | return Object.keys(this.control.errors)
37 | .map((key) => {
38 | return {
39 | name: key,
40 | value: this.control.errors[key]
41 | }
42 | })
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/examples/angular4/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioPhoton/angular-alternative-validation/a72f551819bc716c870ce086c7f6c404be244384/examples/angular4/src/assets/.gitkeep
--------------------------------------------------------------------------------
/examples/angular4/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/examples/angular4/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/examples/angular4/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioPhoton/angular-alternative-validation/a72f551819bc716c870ce086c7f6c404be244384/examples/angular4/src/favicon.ico
--------------------------------------------------------------------------------
/examples/angular4/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TestAngularFormatterParser
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/angular4/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 |
--------------------------------------------------------------------------------
/examples/angular4/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 Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/weak-map';
35 | // import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
41 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | import 'core-js/es6/reflect';
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
50 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
51 |
52 |
53 |
54 | /***************************************************************************************************
55 | * Zone JS is required by Angular itself.
56 | */
57 | import 'zone.js/dist/zone'; // Included with Angular CLI.
58 |
59 |
60 |
61 | /***************************************************************************************************
62 | * APPLICATION IMPORTS
63 | */
64 |
65 | /**
66 | * Date, currency, decimal and percent pipes.
67 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
68 | */
69 | // import 'intl'; // Run `npm install --save intl`.
70 | /**
71 | * Need to import at least one locale-data with intl.
72 | */
73 | // import 'intl/locale-data/jsonp/en';
74 |
--------------------------------------------------------------------------------
/examples/angular4/src/styles.scss:
--------------------------------------------------------------------------------
1 |
2 | // FONT AWESOME ----------------------------------------------------------------
3 | $fa-font-path : '../node_modules/font-awesome/fonts';
4 |
5 | @import "~font-awesome/scss/variables";
6 | @import "~font-awesome/scss/mixins";
7 | @import "~font-awesome/scss/path";
8 |
9 | @import '~font-awesome/scss/path';
10 | @import '~font-awesome/scss/core';
11 | @import '~font-awesome/scss/larger';
12 | @import '~font-awesome/scss/fixed-width';
13 | @import '~font-awesome/scss/list';
14 | @import '~font-awesome/scss/bordered-pulled';
15 | @import '~font-awesome/scss/animated';
16 | @import '~font-awesome/scss/rotated-flipped';
17 | @import '~font-awesome/scss/stacked';
18 | @import '~font-awesome/scss/icons';
19 | @import '~font-awesome/scss/screen-reader';
20 |
21 |
22 | // ---------------------------------------------------------------- FONT AWESOME
23 |
24 | // BOOTSTRAP 4 -----------------------------------------------------------------
25 | // Core variables and mixins
26 | @import "~bootstrap/scss/variables";
27 | @import "~bootstrap/scss/mixins";
28 | @import "~bootstrap/scss/custom";
29 |
30 |
31 | // Reset and dependencies
32 | @import '~bootstrap/scss/normalize';
33 | @import '~bootstrap/scss/print';
34 |
35 | // Core CSS
36 | @import '~bootstrap/scss/reboot';
37 | @import '~bootstrap/scss/type';
38 | @import '~bootstrap/scss/images';
39 | @import '~bootstrap/scss/code';
40 | @import '~bootstrap/scss/grid';
41 | @import '~bootstrap/scss/tables';
42 | @import '~bootstrap/scss/forms';
43 | @import '~bootstrap/scss/buttons';
44 |
45 | // Components
46 | @import '~bootstrap/scss/transitions';
47 | @import '~bootstrap/scss/dropdown';
48 | @import '~bootstrap/scss/button-group';
49 | @import '~bootstrap/scss/input-group';
50 | @import '~bootstrap/scss/custom-forms';
51 | @import '~bootstrap/scss/nav';
52 | @import '~bootstrap/scss/navbar';
53 | @import '~bootstrap/scss/card';
54 | @import '~bootstrap/scss/breadcrumb';
55 | @import '~bootstrap/scss/pagination';
56 | @import '~bootstrap/scss/badge';
57 | @import '~bootstrap/scss/jumbotron';
58 | @import '~bootstrap/scss/alert';
59 | @import '~bootstrap/scss/progress';
60 | @import '~bootstrap/scss/media';
61 | @import '~bootstrap/scss/list-group';
62 | @import '~bootstrap/scss/responsive-embed';
63 | @import '~bootstrap/scss/close';
64 |
65 | // Components w/ JavaScript
66 | @import '~bootstrap/scss/modal';
67 | @import '~bootstrap/scss/tooltip';
68 | @import '~bootstrap/scss/popover';
69 | @import '~bootstrap/scss/carousel';
70 |
71 | // Utility classes
72 | @import '~bootstrap/scss/utilities';
73 |
74 | // ----------------------------------------------------------------- BOOTSTRAP 4
75 |
--------------------------------------------------------------------------------
/examples/angular4/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/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare const __karma__: any;
17 | declare const require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/examples/angular4/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "es2015",
6 | "baseUrl": "",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/examples/angular4/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "baseUrl": "",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/angular4/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/angular4/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "baseUrl": "src",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "moduleResolution": "node",
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "node_modules/@types"
14 | ],
15 | "lib": [
16 | "es2016",
17 | "dom"
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/angular4/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "eofline": true,
15 | "forin": true,
16 | "import-blacklist": [
17 | true,
18 | "rxjs"
19 | ],
20 | "import-spacing": true,
21 | "indent": [
22 | true,
23 | "spaces"
24 | ],
25 | "interface-over-type-literal": true,
26 | "label-position": true,
27 | "max-line-length": [
28 | true,
29 | 140
30 | ],
31 | "member-access": false,
32 | "member-ordering": [
33 | true,
34 | "static-before-instance",
35 | "variables-before-functions"
36 | ],
37 | "no-arg": true,
38 | "no-bitwise": true,
39 | "no-console": [
40 | true,
41 | "debug",
42 | "info",
43 | "time",
44 | "timeEnd",
45 | "trace"
46 | ],
47 | "no-construct": true,
48 | "no-debugger": true,
49 | "no-duplicate-super": true,
50 | "no-empty": false,
51 | "no-empty-interface": true,
52 | "no-eval": true,
53 | "no-inferrable-types": [
54 | true,
55 | "ignore-params"
56 | ],
57 | "no-misused-new": true,
58 | "no-non-null-assertion": false,
59 | "no-shadowed-variable": true,
60 | "no-string-literal": false,
61 | "no-string-throw": true,
62 | "no-switch-case-fall-through": true,
63 | "no-trailing-whitespace": true,
64 | "no-unnecessary-initializer": true,
65 | "no-unused-expression": true,
66 | "no-use-before-declare": true,
67 | "no-var-keyword": true,
68 | "object-literal-sort-keys": false,
69 | "one-line": [
70 | true,
71 | "check-open-brace",
72 | "check-catch",
73 | "check-else",
74 | "check-whitespace"
75 | ],
76 | "prefer-const": true,
77 | "quotemark": [
78 | true,
79 | "single"
80 | ],
81 | "radix": true,
82 | "semicolon": [
83 | "always"
84 | ],
85 | "triple-equals": [
86 | true,
87 | "allow-null-check"
88 | ],
89 | "typedef-whitespace": [
90 | true,
91 | {
92 | "call-signature": "nospace",
93 | "index-signature": "nospace",
94 | "parameter": "nospace",
95 | "property-declaration": "nospace",
96 | "variable-declaration": "nospace"
97 | }
98 | ],
99 | "typeof-compare": true,
100 | "unified-signatures": true,
101 | "variable-name": false,
102 | "whitespace": [
103 | true,
104 | "check-branch",
105 | "check-decl",
106 | "check-operator",
107 | "check-separator",
108 | "check-type"
109 | ],
110 | "directive-selector": [
111 | false,
112 | "attribute"
113 | ],
114 | "component-selector": [
115 | true,
116 | "element",
117 | "kebab-case"
118 | ],
119 | "use-input-property-decorator": true,
120 | "use-output-property-decorator": true,
121 | "use-host-property-decorator": true,
122 | "no-input-rename": false,
123 | "no-output-rename": false,
124 | "use-life-cycle-interface": true,
125 | "use-pipe-transform-interface": true,
126 | "component-class-suffix": true,
127 | "directive-class-suffix": true,
128 | "no-access-missing-member": true,
129 | "templates-use-public": true,
130 | "invoke-injectable": true
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var gulp = require('gulp'),
3 | path = require('path'),
4 | ngc = require('@angular/compiler-cli/src/main').main,
5 | rollup = require('gulp-rollup'),
6 | rename = require('gulp-rename'),
7 | del = require('del'),
8 | runSequence = require('run-sequence'),
9 | inlineResources = require('./tools/gulp/inline-resources');
10 |
11 | const rootFolder = path.join(__dirname);
12 | const srcFolder = path.join(rootFolder, 'src');
13 | const tmpFolder = path.join(rootFolder, '.tmp');
14 | const buildFolder = path.join(rootFolder, 'build');
15 | const distFolder = path.join(rootFolder, 'dist');
16 |
17 | /**
18 | * 1. Delete /dist folder
19 | */
20 | gulp.task('clean:dist', function () {
21 |
22 | // Delete contents but not dist folder to avoid broken npm links
23 | // when dist directory is removed while npm link references it.
24 | return deleteFolders([distFolder + '/**', '!' + distFolder]);
25 | });
26 |
27 | /**
28 | * 2. Clone the /src folder into /.tmp. If an npm link inside /src has been made,
29 | * then it's likely that a node_modules folder exists. Ignore this folder
30 | * when copying to /.tmp.
31 | */
32 | gulp.task('copy:source', function () {
33 | return gulp.src([`${srcFolder}/**/*`, `!${srcFolder}/node_modules`])
34 | .pipe(gulp.dest(tmpFolder));
35 | });
36 |
37 | /**
38 | * 3. Inline template (.html) and style (.css) files into the the component .ts files.
39 | * We do this on the /.tmp folder to avoid editing the original /src files
40 | */
41 | gulp.task('inline-resources', function () {
42 | return Promise.resolve()
43 | .then(() => inlineResources(tmpFolder));
44 | });
45 |
46 |
47 | /**
48 | * 4. Run the Angular compiler, ngc, on the /.tmp folder. This will output all
49 | * compiled modules to the /build folder.
50 | */
51 | gulp.task('ngc', function () {
52 | return ngc({
53 | project: `${tmpFolder}/tsconfig.es5.json`
54 | })
55 | .then((exitCode) => {
56 | if (exitCode === 1) {
57 | // This error is caught in the 'compile' task by the runSequence method callback
58 | // so that when ngc fails to compile, the whole compile process stops running
59 | throw new Error('ngc compilation failed');
60 | }
61 | });
62 | });
63 |
64 | /**
65 | * 5. Run rollup inside the /build folder to generate our Flat ES module and place the
66 | * generated file into the /dist folder
67 | */
68 | gulp.task('rollup:fesm', function () {
69 | return gulp.src(`${buildFolder}/**/*.js`)
70 | // transform the files here.
71 | .pipe(rollup({
72 |
73 | // Bundle's entry point
74 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry
75 | entry: `${buildFolder}/index.js`,
76 |
77 | // Allow mixing of hypothetical and actual files. "Actual" files can be files
78 | // accessed by Rollup or produced by plugins further down the chain.
79 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system
80 | // when subdirectories are used in the `src` directory.
81 | allowRealFiles: true,
82 |
83 | // A list of IDs of modules that should remain external to the bundle
84 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external
85 | external: [
86 | '@angular/core',
87 | '@angular/common'
88 | ],
89 |
90 | // Format of generated bundle
91 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format
92 | format: 'es'
93 | }))
94 | .pipe(gulp.dest(distFolder));
95 | });
96 |
97 | /**
98 | * 6. Run rollup inside the /build folder to generate our UMD module and place the
99 | * generated file into the /dist folder
100 | */
101 | gulp.task('rollup:umd', function () {
102 | return gulp.src(`${buildFolder}/**/*.js`)
103 | // transform the files here.
104 | .pipe(rollup({
105 |
106 | // Bundle's entry point
107 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry
108 | entry: `${buildFolder}/index.js`,
109 |
110 | // Allow mixing of hypothetical and actual files. "Actual" files can be files
111 | // accessed by Rollup or produced by plugins further down the chain.
112 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system
113 | // when subdirectories are used in the `src` directory.
114 | allowRealFiles: true,
115 |
116 | // A list of IDs of modules that should remain external to the bundle
117 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external
118 | external: [
119 | '@angular/core',
120 | '@angular/common'
121 | ],
122 |
123 | // Format of generated bundle
124 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format
125 | format: 'umd',
126 |
127 | // Export mode to use
128 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#exports
129 | exports: 'named',
130 |
131 | // The name to use for the module for UMD/IIFE bundles
132 | // (required for bundles with exports)
133 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#modulename
134 | moduleName: 'angular-alternative-validation',
135 |
136 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals
137 | globals: {
138 | typescript: 'ts'
139 | }
140 |
141 | }))
142 | .pipe(rename('angular-alternative-validation.umd.js'))
143 | .pipe(gulp.dest(distFolder));
144 | });
145 |
146 | /**
147 | * 7. Copy all the files from /build to /dist, except .js files. We ignore all .js from /build
148 | * because with don't need individual modules anymore, just the Flat ES module generated
149 | * on step 5.
150 | */
151 | gulp.task('copy:build', function () {
152 | return gulp.src([`${buildFolder}/**/*`, `!${buildFolder}/**/*.js`])
153 | .pipe(gulp.dest(distFolder));
154 | });
155 |
156 | /**
157 | * 8. Copy package.json from /src to /dist
158 | */
159 | gulp.task('copy:manifest', function () {
160 | return gulp.src([`${srcFolder}/package.json`])
161 | .pipe(gulp.dest(distFolder));
162 | });
163 |
164 | /**
165 | * 9. Copy README.md from / to /dist
166 | */
167 | gulp.task('copy:readme', function () {
168 | return gulp.src([path.join(rootFolder, 'README.MD')])
169 | .pipe(gulp.dest(distFolder));
170 | });
171 |
172 | /**
173 | * 10. Delete /.tmp folder
174 | */
175 | gulp.task('clean:tmp', function () {
176 | return deleteFolders([tmpFolder]);
177 | });
178 |
179 | /**
180 | * 11. Delete /build folder
181 | */
182 | gulp.task('clean:build', function () {
183 | return deleteFolders([buildFolder]);
184 | });
185 |
186 | gulp.task('compile', function () {
187 | runSequence(
188 | 'clean:dist',
189 | 'copy:source',
190 | 'inline-resources',
191 | 'ngc',
192 | 'rollup:fesm',
193 | 'rollup:umd',
194 | 'copy:build',
195 | 'copy:manifest',
196 | 'copy:readme',
197 | 'clean:build',
198 | 'clean:tmp',
199 | function (err) {
200 | if (err) {
201 | console.log('ERROR:', err.message);
202 | deleteFolders([distFolder, tmpFolder, buildFolder]);
203 | } else {
204 | console.log('Compilation finished succesfully');
205 | }
206 | });
207 | });
208 |
209 | /**
210 | * Watch for any change in the /src folder and compile files
211 | */
212 | gulp.task('watch', function () {
213 | gulp.watch(`${srcFolder}/**/*`, ['compile']);
214 | });
215 |
216 | gulp.task('clean', ['clean:dist', 'clean:tmp', 'clean:build']);
217 |
218 | gulp.task('build', ['clean', 'compile']);
219 | gulp.task('build:watch', ['build', 'watch']);
220 | gulp.task('default', ['build:watch']);
221 |
222 | /**
223 | * Deletes the specified folder
224 | */
225 | function deleteFolders(folders) {
226 | return del(folders);
227 | }
228 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | var cfg = {
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-phantomjs-launcher'),
12 | require('karma-jasmine-html-reporter'),
13 | require('karma-coverage-istanbul-reporter'),
14 | require('@angular/cli/plugins/karma')
15 | ],
16 | client:{
17 | clearContext: false // leave Jasmine Spec Runner output visible in browser
18 | },
19 | coverageIstanbulReporter: {
20 | reports: [ 'html', 'lcovonly' ],
21 | fixWebpackSourcePaths: true
22 | },
23 | angularCli: {
24 | environment: 'dev'
25 | },
26 | reporters: ['progress', 'kjhtml'],
27 | port: 9876,
28 | colors: true,
29 | logLevel: config.LOG_INFO,
30 | autoWatch: true,
31 | browsers: ['Chrome'],
32 | customLaunchers: {
33 | TRAVIS_CI: {
34 | base: 'PhantomJS'
35 | }
36 | },
37 | singleRun: true
38 | };
39 |
40 |
41 | if (process.env.TRAVIS) {
42 | cfg.browsers = ['TRAVIS_CI'];
43 | cfg.singleRun = true;
44 | }
45 |
46 | config.set(cfg);
47 | };
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-alternative-validation",
3 | "version": "0.3.6",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "gulp build",
9 | "test": "ng test",
10 | "coverage": "ng test --progress=false --watch=false --code-coverage",
11 | "lint": "ng lint",
12 | "e2e": "ng e2e",
13 | "build:watch": "gulp",
14 | "docs": "npm run docs:build",
15 | "docs:build": "compodoc -p tsconfig.json -n angular-alternative-validation -d docs --hideGenerator",
16 | "docs:serve": "npm run docs:build -- -s",
17 | "docs:watch": "npm run docs:build -- -s -w",
18 | "update-example": "npm run build && copyfiles -u 1 dist/**/* examples/angular4/node_modules/angular-alternative-validation",
19 | "changelog": "conventional-changelog --pkg ./src/package.json -p angular -i CHANGELOG.md -s -r 0",
20 | "recommended-bump": "conventional-recommended-bump -p angular",
21 | "github-release": "conventional-github-releaser -p angular -r 0",
22 | "release": "npm publish dist"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/BioPhoton/angular-alternative-validation"
27 | },
28 | "author": {
29 | "name": "Michael Hladky",
30 | "email": "michael@hladky.at"
31 | },
32 | "keywords": [
33 | "angular"
34 | ],
35 | "bugs": {
36 | "url": "https://github.com/BioPhoton/angular-alternative-validation/issues"
37 | },
38 | "private": true,
39 | "dependencies": {
40 | "@angular/animations": "^4.0.0",
41 | "@angular/common": "^4.0.0",
42 | "@angular/compiler": "^4.0.0",
43 | "@angular/core": "^4.0.0",
44 | "@angular/forms": "^4.0.0",
45 | "@angular/http": "^4.0.0",
46 | "@angular/platform-browser": "^4.0.0",
47 | "@angular/platform-browser-dynamic": "^4.0.0",
48 | "@angular/router": "^4.0.0",
49 | "core-js": "^2.4.1",
50 | "rxjs": "^5.4.1",
51 | "zone.js": "^0.8.14"
52 | },
53 | "devDependencies": {
54 | "@angular/cli": "1.2.2",
55 | "@angular/compiler-cli": "^4.0.0",
56 | "@angular/language-service": "^4.0.0",
57 | "@types/jasmine": "~2.5.53",
58 | "@types/jasminewd2": "~2.0.2",
59 | "@types/node": "~6.0.60",
60 | "codecov.io": "^0.1.6",
61 | "codelyzer": "~3.0.1",
62 | "coveralls": "^2.13.1",
63 | "intl": "^1.2.5",
64 | "gulp": "^3.9.1",
65 | "gulp-rename": "^1.2.2",
66 | "gulp-rollup": "^2.13.0",
67 | "node-sass-tilde-importer": "^1.0.0",
68 | "run-sequence": "^1.2.2",
69 | "jasmine-core": "~2.6.2",
70 | "jasmine-spec-reporter": "~4.1.0",
71 | "karma": "~1.7.0",
72 | "karma-chrome-launcher": "~2.1.1",
73 | "karma-cli": "~1.0.1",
74 | "karma-coverage-istanbul-reporter": "^1.2.1",
75 | "karma-jasmine": "~1.1.0",
76 | "karma-jasmine-html-reporter": "^0.2.2",
77 | "karma-phantomjs-launcher": "^1.0.4",
78 | "protractor": "~5.1.2",
79 | "ts-node": "~3.0.4",
80 | "tslint": "~5.3.2",
81 | "typescript": "~2.3.3"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/resources/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioPhoton/angular-alternative-validation/a72f551819bc716c870ce086c7f6c404be244384/resources/demo.gif
--------------------------------------------------------------------------------
/scripts/check-remote.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 |
3 | # rebuilds and test the latest verstion of the repository (a succeeded travis build is precondition)
4 |
5 | # checks the status of the last build of the current repository
6 | # --no-interactive disables the interactive mode
7 | # source: https://github.com/travis-ci/travis.rb/blob/master/README.md
8 | $state = travis status --no-interactive
9 | echo $state
10 | if ( $state -ne "passed")
11 | {
12 | Write-Host "Invalid travis state $state. State should be passed" -foregroundcolor "red"
13 | Exit
14 | }
15 | Write-Host "checked travis state" -foregroundcolor "green"
16 | # deletes the node_modules folder (move them into trash, more reversable)
17 | # trash node_modules
18 | Write-Host "trashed node_modules" -foregroundcolor "green"
19 | # pulls the latest version
20 | git pull --rebase
21 | Write-Host "git clean and up to date" -foregroundcolor "green"
22 |
--------------------------------------------------------------------------------
/scripts/create-changelog.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 |
3 | # create changelog
4 |
5 | # copy the src/package.json
6 | # we copy it to have the initial state saved.
7 | # we bump the version update the changelog
8 | # after doing this we use the real package.json and do another version bump
9 | # there to have change log and version bump in separate commits
10 | Copy-Item .\src\package.json "src\_package.json"
11 | # Detect what commit message convention your repository is using
12 | # source: https://github.com/conventional-changelog/conventional-commits-detector/blob/master/README.md
13 | # $preset stores the output of conventional-commits-detector which is angular
14 | $preset = (conventional-commits-detector)
15 | # echo prints a value to screen
16 | # ensures that a convention was detected
17 | echo $preset
18 | # Detect the recommended bump type by the conventional-commit standard
19 | # source: https://github.com/conventional-changelog-archived-repos/conventional-recommended-bump/blob/master/README.md
20 | # $bump stores the recommended bump type
21 | $bump = (conventional-recommended-bump -p angular)
22 | # echo prints a value to screen
23 | # ensures that a bump type was detected
24 | echo $bump
25 | # npm version $bump bumps the version specified in $bump and write the new data back to package.json
26 | # If you run npm version in a git repo, it will also create a version commit and tag.
27 | # This behavior is disabled by --no-git-tag-version
28 | # the var $bump specifies the segment of the version code to bump
29 | cd .\src
30 | npm --no-git-tag-version version $bump
31 | cd ..
32 | # conventional-changelog creates a chagnelog markdown from commits
33 | # -i Read the CHANGELOG from this file
34 | # CHANGELOG.md it the name of the file to read from
35 | # -s Outputting to the infile so you don't need to specify the same file as outfile
36 | # -p Name of the preset you want to use. In this case it is angular that is stored in $preset
37 | conventional-changelog -i CHANGELOG.md -s -p $preset
38 | # add CHANGELOG.md to the commit
39 | git add CHANGELOG.md
40 | # get the content of package.json and json-parse the value
41 | $package = (Get-Content ".\src\package.json" -Raw) | ConvertFrom-Json
42 | $version = $package.version
43 | # commit with comment
44 | git commit -m"docs(CHANGELOG): $version"
45 | # run build again because we want to have the new version in the dist folder
46 | npm run build
47 | # Replace the already bumped package.json with the _package.json initial copy
48 | trash .\src\package.json
49 | Rename-Item -Path ".\src\_package.json" -NewName "package.json"
50 | Write-Host "created changelog $preset" -foregroundcolor "green"
51 |
--------------------------------------------------------------------------------
/scripts/detect-project-settings.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 | $preset = (conventional-commits-detector)
3 | # echo prints a value to screen
4 | # ensures that a convention was detected
5 | echo $preset
6 | # Detect the recommended bump type by the conventional-commit standard
7 | # source: https://github.com/conventional-changelog-archived-repos/conventional-recommended-bump/blob/master/README.md
8 | # $bump stores the recommended bump type
9 | $bump = (conventional-recommended-bump -p angular)
10 | # echo prints a value to screen
11 | # ensures that a bump type was detected
12 | echo $bump
13 | # npm version $bump bumps the version specified in $bump and write the new data back to package.json
14 | # If you run npm version in a git repo, it will also create a version commit and tag.
15 | # This behavior is disabled by --no-git-tag-version
16 | # the var $bump specifies the segment of the version code to bump
17 | Write-Host "created changelog $preset" -foregroundcolor "green"
18 |
--------------------------------------------------------------------------------
/scripts/github-release.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 |
3 | # release on git and npm
4 |
5 | # Make a new GitHub release from git metadata based on your commit-convention. In this case angular convention
6 | # source: https://github.com/conventional-changelog/conventional-github-releaser/blob/master/README.md
7 | conventional-github-releaser -p $preset
8 | Write-Host "created github release" -foregroundcolor "green"
9 |
10 | # publish new version on npm
11 | cd .\dist
12 | npm publish
13 | Write-Host "published on npm :-)" -foregroundcolor "green"
14 |
--------------------------------------------------------------------------------
/scripts/install.ps1:
--------------------------------------------------------------------------------
1 | # prerequisites:
2 | npm i -g trash-cli conventional-recommended-bump conventional-changelog conventional-github-releaser conventional-commits-detector json
3 |
--------------------------------------------------------------------------------
/scripts/release.ps1:
--------------------------------------------------------------------------------
1 | # npm publish with goodies
2 | # prerequisites:
3 | # `npm install -g trash conventional-recommended-bump conventional-changelog conventional-github-releaser conventional-commits-detector json`
4 | # `np` with optional argument `patch`/`minor`/`major`/``
5 | # defaults to conventional-recommended-bump
6 | # and optional argument preset `angular`/ `jquery` ...
7 | # defaults to conventional-commits-detector
8 |
9 | $ErrorActionPreference = "Stop"
10 |
11 | # rebuilds and test the latest version of the repository (a succeeded travis build is precondition)
12 |
13 | # checks the status of the last build of the current repository
14 | # --no-interactive disables the interactive mode
15 | # source: https://github.com/travis-ci/travis.rb/blob/master/README.md
16 | $state = travis status --no-interactive
17 | echo $state
18 | if ( $state -ne "passed")
19 | {
20 | Write-Host "Invalid travis state $state. State should be passed" -foregroundcolor "red"
21 | Exit
22 | }
23 | Write-Host "checked travis state" -foregroundcolor "green"
24 | # deletes the node_modules folder (move them into trash, more reversable)
25 | # trash node_modules
26 | Write-Host "trashed node_modules" -foregroundcolor "green"
27 | # pulls the latest version
28 | git pull --rebase
29 | # installs the node dependencies
30 | # npm install
31 | # run unit tests
32 | karma start karma.conf.js
33 | Write-Host "run tests" -foregroundcolor "green"
34 |
35 | # create changelog
36 |
37 | # copy the src/package.json
38 | # we copy it to have the initial state saved.
39 | # we bump the version update the changelog
40 | # after doing this we use the real package.json and do another version bump
41 | # there to have change log and version bump in separate commits
42 | Copy-Item .\src\package.json "src\_package.json"
43 | # Detect what commit message convention your repository is using
44 | # source: https://github.com/conventional-changelog/conventional-commits-detector/blob/master/README.md
45 | # $preset stores the output of conventional-commits-detector which is angular
46 | $preset = (conventional-commits-detector)
47 | # echo prints a value to screen
48 | # ensures that a convention was detected
49 | echo $preset
50 | # Detect the recommended bump type by the conventional-commit standard
51 | # source: https://github.com/conventional-changelog-archived-repos/conventional-recommended-bump/blob/master/README.md
52 | # $bump stores the recommended bump type
53 | $bump = (conventional-recommended-bump -p angular)
54 | # echo prints a value to screen
55 | # ensures that a bump type was detected
56 | echo $bump
57 | # npm version $bump bumps the version specified in $bump and write the new data back to package.json
58 | # If you run npm version in a git repo, it will also create a version commit and tag.
59 | # This behavior is disabled by --no-git-tag-version
60 | # the var $bump specifies the segment of the version code to bump
61 | cd .\src
62 | npm --no-git-tag-version version $bump
63 | cd ..
64 | # conventional-changelog creates a chagnelog markdown from commits
65 | # -i Read the CHANGELOG from this file
66 | # CHANGELOG.md it the name of the file to read from
67 | # -s Outputting to the infile so you don't need to specify the same file as outfile
68 | # -p Name of the preset you want to use. In this case it is angular that is stored in $preset
69 | conventional-changelog -i CHANGELOG.md -s -p $preset
70 | # add CHANGELOG.md to the commit
71 | git add CHANGELOG.md
72 | # get the content of package.json and json-parse the value
73 | $package = (Get-Content ".\src\package.json" -Raw) | ConvertFrom-Json
74 | $version = $package.version
75 | # commit with comment
76 | git commit -m"docs(CHANGELOG): $version"
77 | # run build again because we want to have the new version in the dist folder
78 | npm run build
79 | # Replace the already bumped package.json with the _package.json initial copy
80 | trash .\src\package.json
81 | Rename-Item -Path ".\src\_package.json" -NewName "package.json"
82 | Write-Host "created changelog $preset" -foregroundcolor "green"
83 |
84 |
85 | # create version bump
86 |
87 | # npm version $bump bumps the version specified in $bump and write the new data back to package.json
88 | # -m will set a commit message with the version placed by %s
89 | cd .\src
90 | npm --no-git-tag-version version $bump
91 | git add .\package.json
92 | git commit -m "chore(release): $version ($bump)"
93 | git tag $version
94 | cd ..
95 | # pushed the commit
96 | # --follow-tags also pushed the new tags
97 | # source: https://git-scm.com/docs/git-push
98 | git push --follow-tags
99 | Write-Host "pushed repo" -foregroundcolor "green"
100 |
101 | # release on git and npm
102 |
103 | # Make a new GitHub release from git metadata based on your commit-convention. In this case angular convention
104 | # source: https://github.com/conventional-changelog/conventional-github-releaser/blob/master/README.md
105 | conventional-github-releaser -p $preset
106 | Write-Host "created github release" -foregroundcolor "green"
107 |
108 | # publish new version on npm
109 | cd .\dist
110 | npm publish
111 | cd ..
112 | # Write-Host "published on npm :-)" -foregroundcolor "green"
113 |
--------------------------------------------------------------------------------
/scripts/setup-and-test.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 |
3 | # installs the node dependencies
4 | # npm install
5 | Write-Host "project setup done" -foregroundcolor "green"
6 |
7 | # run unit tests
8 | karma start karma.conf.js
9 | Write-Host "run tests" -foregroundcolor "green"
10 |
--------------------------------------------------------------------------------
/scripts/version-bump.ps1:
--------------------------------------------------------------------------------
1 | # create version bump
2 |
3 | # npm version $bump bumps the version specified in $bump and write the new data back to package.json
4 | # -m will set a commit message with the version placed by %s
5 | cd .\src
6 | npm --no-git-tag-version version $bump
7 | git add .\package.json
8 | git commit -m "chore(release): $version ($bump)"
9 | git tag $version
10 | cd ..
11 | # pushed the commit
12 | # --follow-tags also pushed the new tags
13 | # source: https://git-scm.com/docs/git-push
14 | git push --follow-tags
15 | Write-Host "pushed repo" -foregroundcolor "green"
16 |
--------------------------------------------------------------------------------
/src/alternative-validation.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import {Component, DebugElement, ViewChild} from '@angular/core';
2 | import {ComponentFixture, TestBed} from '@angular/core/testing';
3 | import {AbstractControl, FormControl, FormGroup} from '@angular/forms';
4 | import {By} from '@angular/platform-browser';
5 | import {AlternativeValidationDirective} from './alternative-validation.directive';
6 | import {AlternativeValidationModule} from './index';
7 | import {IAlternativeValidationConfig} from './struct/alternative-validation-config';
8 | import {ValidationCollectorService} from './validation-collector.service';
9 |
10 | @Component({
11 | template: `
12 |
16 | `
17 | })
18 | class TestComponent {
19 | fg: FormGroup = new FormGroup({
20 | 'target1': new FormControl(''),
21 | });
22 | config: IAlternativeValidationConfig = {
23 | validator: [
24 | {name: 'required'},
25 | {name: 'minLength', params: [3]}
26 | ]
27 | };
28 |
29 | @ViewChild(AlternativeValidationDirective)
30 | exposedTarget1;
31 | }
32 |
33 | describe('AlternativeValidationDirective', () => {
34 |
35 | let hostComponents: TestComponent;
36 | let alternativeControl: any;
37 | let fixture: ComponentFixture;
38 | let el: DebugElement;
39 |
40 | let target1Input: DebugElement;
41 | let target1InputControl: AbstractControl;
42 |
43 | function setInputValue(inputElem: DebugElement, value) {
44 | inputElem.nativeElement.value = value;
45 | inputElem.triggerEventHandler('input', {target: {value: value}});
46 | fixture.detectChanges();
47 | }
48 |
49 | beforeEach(() => {
50 | TestBed.configureTestingModule({
51 | imports: [
52 | AlternativeValidationModule.forRoot()
53 | ],
54 | declarations: [
55 | TestComponent
56 | ],
57 | providers: [
58 | ValidationCollectorService
59 | ]
60 | });
61 | fixture = TestBed.createComponent(TestComponent);
62 | hostComponents = fixture.componentInstance;
63 | el = fixture.debugElement;
64 |
65 | target1Input = el.query(By.css('#target1'));
66 | target1InputControl = hostComponents.fg.get('target1');
67 | alternativeControl = hostComponents.exposedTarget1.control;
68 | });
69 |
70 | it('should create an instance', () => {
71 | fixture.detectChanges();
72 | alternativeControl = hostComponents.exposedTarget1.control;
73 | expect(hostComponents).toBeTruthy();
74 | expect(target1Input).toBeTruthy();
75 | expect(target1InputControl).toBeTruthy();
76 | });
77 |
78 | it('should be accessable over @ViewChild', () => {
79 | fixture.detectChanges();
80 | alternativeControl = hostComponents.exposedTarget1.control;
81 | expect(alternativeControl).toBeTruthy();
82 | expect(alternativeControl.valid).toBe(false);
83 | });
84 |
85 |
86 | it('should stay valid if input changes', () => {
87 | fixture.detectChanges();
88 | alternativeControl = hostComponents.exposedTarget1.control;
89 | expect(target1InputControl.value).toBe('');
90 | expect(target1InputControl.valid).toBe(true);
91 |
92 | setInputValue(target1Input, '12');
93 | expect(target1InputControl.value).toBe('12');
94 | expect(target1InputControl.valid).toBe(true);
95 |
96 | setInputValue(target1Input, '123');
97 | expect(target1InputControl.value).toBe('123');
98 | expect(target1InputControl.valid).toBe(true);
99 | });
100 |
101 | it('should change state of alternative validation when input changes', () => {
102 | fixture.detectChanges();
103 | alternativeControl = hostComponents.exposedTarget1.control;
104 |
105 | expect(target1InputControl.value).toBe('');
106 | expect(alternativeControl.value).toBe('');
107 | expect(target1InputControl.valid).toBe(true);
108 | expect(alternativeControl.valid).toBe(false);
109 | expect(alternativeControl.hasError('required')).toBe(true);
110 | expect(alternativeControl.hasError('minlength')).toBe(false);
111 |
112 | setInputValue(target1Input, '12');
113 | expect(target1InputControl.value).toBe('12');
114 | expect(alternativeControl.value).toBe('12');
115 |
116 | expect(target1InputControl.valid).toBe(true);
117 | expect(alternativeControl.valid).toBe(false);
118 | expect(alternativeControl.hasError('required')).toBe(false);
119 | expect(alternativeControl.hasError('minlength')).toBe(true);
120 |
121 | setInputValue(target1Input, '123');
122 | expect(alternativeControl.value).toBe('123');
123 | expect(target1InputControl.valid).toBe(true);
124 | expect(alternativeControl.valid).toBe(true);
125 | expect(alternativeControl.hasError('required')).toBe(false);
126 | expect(alternativeControl.hasError('minlength')).toBe(false);
127 | });
128 |
129 | it('should track focus', () => {
130 | fixture.detectChanges();
131 | alternativeControl = hostComponents.exposedTarget1.control;
132 | target1Input.triggerEventHandler('focus', {});
133 | expect(hostComponents.exposedTarget1.focus).toBe(true);
134 |
135 | target1Input.triggerEventHandler('blur', {});
136 | expect(hostComponents.exposedTarget1.focus).toBe(false);
137 |
138 | });
139 |
140 | it('should be able to set disabled state', () => {
141 | fixture.detectChanges();
142 |
143 | hostComponents.exposedTarget1.setDisabledState(true);
144 | expect(target1Input.properties.disabled).toBe(true);
145 |
146 | hostComponents.exposedTarget1.setDisabledState(false);
147 | expect(target1Input.properties.disabled).toBe(false);
148 | });
149 |
150 | it('should be able to reset', () => {
151 | fixture.detectChanges();
152 | target1InputControl.setValue('test');
153 | expect(target1InputControl.value).toBe('test');
154 | expect(hostComponents.exposedTarget1.value).toBe('test');
155 |
156 | target1InputControl.reset();
157 | expect(target1InputControl.value).toBeFalsy();
158 | expect(hostComponents.exposedTarget1.value).toBeFalsy();
159 | });
160 |
161 | it('should be able to get the status', () => {
162 | fixture.detectChanges();
163 |
164 | expect(hostComponents.exposedTarget1.status).toBe('INVALID');
165 |
166 | setInputValue(target1Input, '123');
167 | expect(hostComponents.exposedTarget1.status).toBe('VALID');
168 | });
169 |
170 | it('should listen on reset events', () => {
171 | fixture.detectChanges();
172 |
173 | setInputValue(target1Input, '123');
174 | expect(hostComponents.exposedTarget1.status).toBe('VALID');
175 |
176 | hostComponents.exposedTarget1.reset();
177 | expect(hostComponents.exposedTarget1.status).toBe('INVALID');
178 | });
179 |
180 | it('should react to composition events', () => {
181 | target1Input.triggerEventHandler('compositionstart', {});
182 | target1Input.triggerEventHandler('compositionend', {target: {value: 'composition events'}});
183 | });
184 |
185 | });
186 |
--------------------------------------------------------------------------------
/src/alternative-validation.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ElementRef,
4 | forwardRef,
5 | Host,
6 | Inject,
7 | Input,
8 | OnChanges,
9 | OnDestroy,
10 | OnInit,
11 | Optional,
12 | Renderer2,
13 | SimpleChanges,
14 | SkipSelf
15 | } from '@angular/core';
16 | import {
17 | AbstractControl,
18 | AbstractControlDirective,
19 | COMPOSITION_BUFFER_MODE,
20 | ControlContainer,
21 | ControlValueAccessor,
22 | FormControl,
23 | NG_VALUE_ACCESSOR
24 | } from '@angular/forms';
25 | import {ɵgetDOM as getDOM} from '@angular/platform-browser';
26 | import 'rxjs/add/observable/combineLatest';
27 | import 'rxjs/add/operator/filter';
28 | import 'rxjs/add/operator/map';
29 | import 'rxjs/add/operator/switchMap';
30 | import 'rxjs/add/operator/takeUntil';
31 | import {Observable} from 'rxjs/Observable';
32 | import {Subject} from 'rxjs/Subject';
33 | import {IAlternativeValidationConfig} from './struct/alternative-validation-config';
34 | import {ValidationCollectorService} from './validation-collector.service';
35 |
36 | /**
37 | * We must check whether the agent is Android because composition events
38 | * behave differently between iOS and Android.
39 | */
40 | function isAndroid(): boolean {
41 | const userAgent = getDOM() ? getDOM().getUserAgent() : '';
42 | return /android (\d+)/.test(userAgent.toLowerCase());
43 | }
44 |
45 | const CONTROL_VALUE_ACCESSOR = {
46 | name: 'alternativeValidationValueAccessor',
47 | provide: NG_VALUE_ACCESSOR,
48 | useExisting: forwardRef(() => AlternativeValidationDirective),
49 | multi: true
50 | };
51 |
52 | @Directive({
53 | selector: '[alternativeValidation]',
54 | providers: [CONTROL_VALUE_ACCESSOR],
55 | host: {
56 | /*
57 | * Listening to the native input event of the host element.
58 | * On input we call the take the value property of the target element end call
59 | * the handleInput function with it. This renders the new value to the view.
60 | */
61 | '(input)': 'handleInput($event.target.value)',
62 | /*
63 | * Listening to the native focus event of the host element.
64 | * On focus we call the internal haldleFocus function
65 | */
66 | '(focus)': 'handleFocus(true)',
67 | /*
68 | * Listening to the native blur event of the host element.
69 | * On blur we call the onTouched function from the formControl
70 | */
71 | '(blur)': 'handleFocus(false)',
72 | /*
73 | * The compositionstart event is fired when the composition of a passage of text is prepared
74 | * (similar to keydown for a keyboard input, but fires with special characters that require
75 | * a sequence of keys and other inputs such as speech recognition or word suggestion on mobile).
76 | */
77 | '(compositionstart)': 'compositionStart()',
78 | /*
79 | * The compositionend event is fired when the composition of a passage of text has been completed
80 | * or cancelled
81 | * (fires with special characters that require a sequence of keys and other inputs such as
82 | * speech recognition or word suggestion on mobile).
83 | */
84 | '(compositionend)': 'compositionEnd($event.target.value)'
85 | },
86 | exportAs: 'alternativeValidation'
87 | })
88 | export class AlternativeValidationDirective extends AbstractControlDirective implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
89 |
90 | // Reference to the fake formControl
91 | control: AbstractControl;
92 |
93 | // Reference to the formControl
94 | realFormControl: AbstractControl;
95 |
96 | @Input()
97 | // The formControlName in the parent
98 | protected formControlName: string;
99 |
100 | // The internal data model
101 | private _value: any = '';
102 |
103 | // The internal focus state
104 | private _focus = false;
105 |
106 | // The internal disabled state
107 | private _disabled: boolean;
108 |
109 | destroy$ = new Subject();
110 |
111 |
112 | // The internal state of composing input
113 | protected composing = false;
114 |
115 | @Input('alternativeValidation')
116 | // The config for the alternative validation in the parent
117 | protected config: IAlternativeValidationConfig;
118 |
119 | private onChange: Function = () => {};
120 | private onTouched: Function = () => {};
121 |
122 | constructor(
123 | protected renderer: Renderer2, protected elementRef: ElementRef,
124 | @Optional() @Inject(COMPOSITION_BUFFER_MODE) protected compositionMode: boolean,
125 | @Optional() @Host() @SkipSelf() private parentFormContainer: ControlContainer,
126 | protected vs: ValidationCollectorService
127 | ) {
128 | super();
129 | if (this.compositionMode == null) {
130 | this.compositionMode = !isAndroid();
131 | }
132 | }
133 |
134 | get focus(): boolean {
135 | return this._focus;
136 | }
137 |
138 | set focus(value: boolean) {
139 | this._focus = value;
140 | }
141 |
142 | /*
143 | * Handel formControl model changes
144 | */
145 | writeValue(value: any): void {
146 | this.renderViewValue(value);
147 | }
148 |
149 | /*
150 | * Registers the controls onChange function
151 | */
152 | registerOnChange(fn: (_: any) => void): void {
153 | this.onChange = fn;
154 | }
155 |
156 | /*
157 | * Registers the controls onTouched function
158 | */
159 | registerOnTouched(fn: () => void): void {
160 | this.onTouched = fn;
161 | }
162 |
163 | /*
164 | * Sets the internal disabled state and renders it to the view
165 | */
166 | setDisabledState(isDisabled: boolean): void {
167 | this._disabled = isDisabled;
168 | this.renderViewDisabled(isDisabled);
169 | }
170 |
171 | /*
172 | * Depending on the compositionMode and the composing state it
173 | * calls writeValueFromViewToModel with new value
174 | */
175 | private handleInput(value: any): void {
176 | if (!this.compositionMode || (this.compositionMode && !this.composing)) {
177 | this.writeValueFromViewToModel(value);
178 | }
179 | }
180 |
181 | /*
182 | * Sets the internal focus state and renders it to the view
183 | * It also calls onTouch if a blur happens
184 | */
185 | private handleFocus(isFocus: boolean): void {
186 | this.focus = isFocus;
187 | if (!isFocus) {
188 | this.onTouched();
189 | this.updateFakeTouched(this.realFormControl.touched);
190 | }
191 | this.renderViewFocus(isFocus);
192 | }
193 |
194 | /*
195 | * Is called when the compositionStart event is fired.
196 | * It sets the internal composing state to true
197 | */
198 | private compositionStart(): void {
199 | this.composing = true;
200 | }
201 |
202 | /*
203 | * Is called when the compositionEnd event is fired
204 | * It sets the internal composing state to false
205 | * and triggers the onChange function with the new value.
206 | */
207 | private compositionEnd(value: any): void {
208 | this.composing = false;
209 | if (this.compositionMode) {
210 | this._value = value;
211 | this.onChange(value);
212 | }
213 | }
214 |
215 | // Directive lifecycle hooks ==================================================================
216 |
217 | ngOnChanges(changes: SimpleChanges) {
218 | this.updateValidators();
219 | }
220 |
221 | ngOnInit(): void {
222 | this.updateFormControlRef();
223 | }
224 |
225 | ngOnDestroy() {
226 | this.destroy$.next(true);
227 | }
228 |
229 | // ControlValueAccessor ==================================================================
230 |
231 | protected writeValueFromViewToModel(value: any) {
232 | if (value !== this._value) {
233 | this._value = value;
234 | this.onChange(value);
235 | this.updateFakeValue(value);
236 | }
237 | }
238 |
239 | protected renderViewValue(value: any) {
240 | const normalizedValue = value == null ? '' : value;
241 | this.renderer.setProperty(this.getInputElementRef(), 'value', normalizedValue);
242 | }
243 |
244 | protected renderViewDisabled(isDisabled: boolean) {
245 | this.renderer.setProperty(this.getInputElementRef(), 'disabled', isDisabled);
246 | }
247 |
248 | protected renderViewFocus(isFocus: boolean): void {
249 | this.renderer.setProperty(this.getInputElementRef(), 'focus', isFocus);
250 | }
251 |
252 | // get a safe ref to the input element
253 | private getInputElementRef(): HTMLInputElement {
254 | let input: HTMLInputElement;
255 | if (this.elementRef.nativeElement.tagName === 'INPUT') {
256 | // directive is used directly on an input element
257 | input = this.elementRef.nativeElement;
258 | } else {
259 | // directive is used on an abstracted input element, `ion-input`, `md-input`, etc
260 | input = this.elementRef.nativeElement.getElementsByTagName('INPUT')[0];
261 | }
262 |
263 | if (!input) {
264 | throw new Error('You can applied the "alternativeValidation" directive only on inputs or elements containing inputs');
265 | }
266 |
267 | return input;
268 | }
269 |
270 | // FormControl ==================================================================
271 |
272 | private updateFormControlRef() {
273 | this.realFormControl = this.parentFormContainer['form'].controls[this.formControlName];
274 |
275 | this.updateFakeControlRef(this.realFormControl.value);
276 | this.setupResetObservable(this.realFormControl);
277 | this.setupDisabledObservable(this.realFormControl);
278 | }
279 |
280 | /*
281 | * custom implementation of status getter
282 | * */
283 | get status(): string {
284 | return this.control ? this.control.status : null;
285 | }
286 |
287 | // Reset handling ==============================================================================
288 |
289 | private setupResetObservable(control: AbstractControl): void {
290 | Observable.combineLatest(control.statusChanges, control.valueChanges)
291 | .takeUntil(this.destroy$.asObservable())
292 | .filter(() => {
293 | const resetState = {
294 | dirty: false,
295 | pristine: true,
296 | touched: false,
297 | untouched: true
298 | };
299 |
300 | return Object
301 | .keys(resetState)
302 | .reduce((state, item) => {
303 | return !state ? false : control[item] === resetState[item];
304 | }, true);
305 | })
306 | .subscribe(() => {
307 | this.onResetEvent();
308 | });
309 | }
310 |
311 | private setupDisabledObservable(control: AbstractControl): void {
312 | Observable.combineLatest(control.statusChanges, control.valueChanges)
313 | .takeUntil(this.destroy$.asObservable())
314 | .map(() => {
315 | const disabledState = {
316 | valid: false,
317 | invalid: false,
318 | status: 'DISABLED'
319 | };
320 |
321 | return Object
322 | .keys(disabledState)
323 | .reduce((state, item) => {
324 | return !state ? false : control[item] === disabledState[item];
325 | }, true);
326 | })
327 | .subscribe((isDisabled) => {
328 | this.onDisableEvent(isDisabled);
329 | });
330 | }
331 |
332 | // Alternative validation ==============================================================================
333 |
334 | private onResetEvent() {
335 | this.control.reset(this.realFormControl.value);
336 | }
337 |
338 | private onDisableEvent(isDisabled: boolean) {
339 | if (!isDisabled) {
340 | this.control.enable();
341 | } else {
342 | this.control.disable();
343 | }
344 | }
345 |
346 | private updateFakeControlRef(formState: any): void {
347 | this.control = new FormControl();
348 | this.updateValidators();
349 | this.control.reset(formState);
350 | }
351 |
352 | private updateValidators(): void {
353 | if (this.config && this.control && this.control instanceof AbstractControl) {
354 | if ('validator' in this.config && Array.isArray(this.config.validator)) {
355 | this.control.setValidators(this.vs.getValidators(this.config.validator));
356 | }
357 | if ('asyncValidator' in this.config && Array.isArray(this.config.asyncValidator)) {
358 | this.control.setValidators(this.vs.getAsyncValidators(this.config.asyncValidator));
359 | }
360 | }
361 | }
362 |
363 | private updateFakeValue(value): void {
364 | if (this.control) {
365 | this.control.setValue(value);
366 | this.control.updateValueAndValidity(value);
367 | this.updateFakeDirty(true);
368 | }
369 | }
370 |
371 | private updateFakeTouched(isTouched: boolean): void {
372 | if (isTouched) {
373 | this.control.markAsTouched();
374 | } else {
375 | this.control.markAsUntouched();
376 | }
377 | }
378 |
379 | private updateFakeDirty(isDirty: boolean): void {
380 | if (isDirty) {
381 | this.control.markAsDirty();
382 | } else {
383 | this.control.markAsPristine();
384 | }
385 | }
386 |
387 | }
388 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import {CommonModule} from '@angular/common';
2 | import {NgModule} from '@angular/core';
3 | import {ReactiveFormsModule} from '@angular/forms';
4 |
5 | import {AlternativeValidationDirective} from './alternative-validation.directive';
6 | import {ValidationCollectorService} from './validation-collector.service';
7 |
8 | export * from './alternative-validation.directive';
9 | export * from './validation-collector.service';
10 |
11 |
12 | @NgModule({
13 | imports: [
14 | CommonModule,
15 | ReactiveFormsModule
16 | ],
17 | declarations: [AlternativeValidationDirective],
18 | exports: [AlternativeValidationDirective, ReactiveFormsModule]
19 | })
20 | export class AlternativeValidationModule {
21 |
22 | static forRoot() {
23 | return {
24 | ngModule: AlternativeValidationModule,
25 | providers: [ValidationCollectorService]
26 | };
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import {enableProdMode} from '@angular/core';
2 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
3 | import {AlternativeValidationModule} from './index';
4 |
5 | enableProdMode();
6 | platformBrowserDynamic().bootstrapModule(AlternativeValidationModule);
7 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-alternative-validation",
3 | "description": "Angular Alternative Validation - The smoothest way to implement validation hints/warnings for your forms",
4 | "version": "1.0.6",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/BioPhoton/angular-alternative-validation"
8 | },
9 | "author": {
10 | "name": "Michael Hladky",
11 | "email": "michael@hladky.at"
12 | },
13 | "keywords": [
14 | "angular",
15 | "validation",
16 | "warning",
17 | "warnings",
18 | "hint",
19 | "hints",
20 | "validation-warning",
21 | "validation-hint",
22 | "alternative validation",
23 | "directive",
24 | "NG_VALIDATORS",
25 | "NG_ASYNC_VALIDATORS",
26 | "ControlValueAccessor",
27 | "AbstractControlDirective"
28 | ],
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/BioPhoton/angular-alternative-validation/issues"
32 | },
33 | "module": "angular-alternative-validation.js",
34 | "typings": "angular-alternative-validation.d.ts",
35 | "peerDependencies": {
36 | "@angular/core": "^4.0.0",
37 | "@angular/forms": "^4.0.0",
38 | "rxjs": "^5.1.0",
39 | "zone.js": "^0.8.4"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | import 'core-js/es6/array';
2 | import 'core-js/es6/date';
3 | import 'core-js/es6/function';
4 | import 'core-js/es6/map';
5 | import 'core-js/es6/math';
6 | import 'core-js/es6/number';
7 | import 'core-js/es6/object';
8 | import 'core-js/es6/parse-float';
9 | import 'core-js/es6/parse-int';
10 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
11 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
12 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
13 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
14 | /** Evergreen browsers require these. **/
15 | import 'core-js/es6/reflect';
16 | import 'core-js/es6/regexp';
17 | import 'core-js/es6/set';
18 | import 'core-js/es6/string';
19 | /**
20 | * This file includes polyfills needed by Angular and is loaded before the app.
21 | * You can add your own extra polyfills to this file.
22 | *
23 | * This file is divided into 2 sections:
24 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
25 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
26 | * file.
27 | *
28 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
29 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
30 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
31 | *
32 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
33 | */
34 | /***************************************************************************************************
35 | * BROWSER POLYFILLS
36 | */
37 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
38 | import 'core-js/es6/symbol';
39 | import 'core-js/es6/weak-map';
40 | import 'core-js/es7/reflect';
41 | /***************************************************************************************************
42 | * APPLICATION IMPORTS
43 | */
44 | /**
45 | * Date, currency, decimal and percent pipes.
46 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
47 | */
48 | import 'intl'; // Run `npm install --save intl`.
49 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
50 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
51 | /***************************************************************************************************
52 | * Zone JS is required by Angular itself.
53 | */
54 | import 'zone.js/dist/zone'; // Included with Angular CLI.
55 | /**
56 | * Need to import at least one locale-data with intl.
57 | */
58 | // import 'intl/locale-data/jsonp/en';
59 |
--------------------------------------------------------------------------------
/src/struct/alternative-validation-config.ts:
--------------------------------------------------------------------------------
1 | import { IValidatorConfig } from './validator-config';
2 | export interface IAlternativeValidationConfig {
3 | validator?: IValidatorConfig[];
4 | asyncValidator?: IValidatorConfig[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/struct/validator-config.ts:
--------------------------------------------------------------------------------
1 | export interface IValidatorConfig {
2 | name: string;
3 | params?: any[];
4 | }
5 |
--------------------------------------------------------------------------------
/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/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare const __karma__: any;
17 | declare const require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/src/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "module": "es2015",
5 | "target": "es5",
6 | "baseUrl": ".",
7 | "stripInternal": true,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "moduleResolution": "node",
11 | "outDir": "../build",
12 | "rootDir": ".",
13 | "lib": [
14 | "es2015",
15 | "dom"
16 | ],
17 | "skipLibCheck": true,
18 | "types": []
19 | },
20 | "angularCompilerOptions": {
21 | "annotateForClosureCompiler": true,
22 | "strictMetadataEmit": true,
23 | "skipTemplateCodegen": true,
24 | "flatModuleOutFile": "angular-alternative-validation.js",
25 | "flatModuleId": "angular-alternative-validation"
26 | },
27 | "files": [
28 | "./index.ts"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "baseUrl": "",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/validation-collector.service.ts:
--------------------------------------------------------------------------------
1 | import {Inject, Injectable, Optional} from '@angular/core';
2 | import {
3 | AsyncValidatorFn,
4 | NG_ASYNC_VALIDATORS,
5 | NG_VALIDATORS,
6 | ValidatorFn,
7 | Validators
8 | } from '@angular/forms';
9 | import {IValidatorConfig} from './struct/validator-config';
10 | @Injectable()
11 | export class ValidationCollectorService {
12 |
13 | constructor(
14 | @Optional() @Inject(NG_VALIDATORS) private NG_VALIDATORS: ValidatorFn[],
15 | @Optional() @Inject(NG_ASYNC_VALIDATORS) private NG_ASYNC_VALIDATORS: AsyncValidatorFn[]
16 | ) {
17 | }
18 |
19 | getValidators(validatorsConfig: IValidatorConfig[]): ValidatorFn[] {
20 | let validators: ValidatorFn[] = [];
21 |
22 | if (validatorsConfig) {
23 | validators = validatorsConfig.map((validatorObj: IValidatorConfig) => {
24 | return this.getValidatorFn(validatorObj.name, validatorObj.params);
25 | });
26 | }
27 | return validators;
28 | }
29 |
30 | getValidatorFn(validatorName: string, validatorArgs?: any[]): ValidatorFn {
31 | if (!validatorName) {
32 | throw new Error('No validation name given to search for');
33 | }
34 | const validatorFn = Validators[validatorName] || this.getCustomValidatorFn(validatorName);
35 |
36 | if (!(typeof validatorFn === 'function')) {
37 | throw new Error(`Validator "${validatorName}" is not provided via NG_VALIDATORS`);
38 | }
39 |
40 | const finalFunction = (validatorArgs) ? validatorFn(...validatorArgs) : validatorFn;
41 |
42 | if (typeof finalFunction !== 'function') {
43 | throw new Error(`Validator "${validatorName}" is not provided a function.
44 | Did you provide params for a validator that don't need them?`);
45 | }
46 |
47 | return finalFunction;
48 | }
49 |
50 | getCustomValidatorFn(validatorName: string): ValidatorFn | undefined {
51 | let validatorFn;
52 |
53 | if (!validatorName) {
54 | throw new Error('No validation name given to search for');
55 | }
56 |
57 | if (this.NG_VALIDATORS) {
58 | validatorFn = this.NG_VALIDATORS.find((fn) => {
59 | return validatorName === fn.name;
60 | });
61 | }
62 |
63 | return validatorFn;
64 | }
65 |
66 | getAsyncValidators(config: IValidatorConfig[]): AsyncValidatorFn[] {
67 | let asyncValidators: AsyncValidatorFn[] = [];
68 |
69 | if (config) {
70 | asyncValidators = config.map((validatorObj: IValidatorConfig) => {
71 | return this.getCustomAsyncValidatorFn(validatorObj.name, validatorObj.params);
72 | });
73 | }
74 | return asyncValidators;
75 | }
76 |
77 | getCustomAsyncValidatorFn(validatorName: string, validatorArgs?: any[]): AsyncValidatorFn {
78 | let asyncValidatorFn;
79 |
80 | if (!validatorName) {
81 | throw new Error('No asyncvalidation name given to search for');
82 | }
83 |
84 | if (this.NG_ASYNC_VALIDATORS) {
85 |
86 | asyncValidatorFn = this.NG_ASYNC_VALIDATORS.find(
87 | (aFn) => {
88 | return validatorName === aFn.name;
89 | });
90 | }
91 |
92 | if (!(typeof asyncValidatorFn === 'function')) {
93 | throw new Error(`Asyncvalidator "${validatorName}" is not provided via NG_ASYNC_VALIDATORS`);
94 | }
95 |
96 | const finalFunction = (validatorArgs) ? asyncValidatorFn(...validatorArgs) : asyncValidatorFn;
97 |
98 | if (typeof finalFunction !== 'function') {
99 | throw new Error(`Asyncvalidator "${validatorName}" is not provided a function.
100 | Did you provide params for a validator that don't need them?`);
101 | }
102 |
103 | return finalFunction;
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/validation-collector.spec.ts:
--------------------------------------------------------------------------------
1 | import {inject, TestBed} from '@angular/core/testing';
2 | import {
3 | AbstractControl,
4 | FormControl,
5 | NG_ASYNC_VALIDATORS,
6 | NG_VALIDATORS,
7 | ValidationErrors,
8 | ValidatorFn
9 | } from '@angular/forms';
10 | import 'rxjs/add/operator/debounceTime';
11 | import 'rxjs/add/operator/delay';
12 | import 'rxjs/add/operator/distinctUntilChanged';
13 | import 'rxjs/add/operator/first';
14 |
15 | import {Observable} from 'rxjs/Observable';
16 | import {IValidatorConfig} from './struct/validator-config';
17 | import {ValidationCollectorService} from './validation-collector.service';
18 |
19 | export function isBlacklistedName(c: AbstractControl): Observable {
20 |
21 | const validatorName = 'isBlacklistedName';
22 | const routValidation$ = new Observable(observer => {
23 |
24 | if (c.value && typeof c.value === 'string' &&
25 | 'abcde'.indexOf(c.value.toString().toLowerCase()) !== -1) {
26 | observer.next({
27 | [validatorName]: {
28 | actual: c.value,
29 | mandatoryChars: 'abcde'
30 | }
31 | });
32 | } else {
33 | observer.next(null);
34 | }
35 | });
36 |
37 | return routValidation$.debounceTime(500).distinctUntilChanged().delay(2000).first();
38 | }
39 |
40 | export function validName(c: AbstractControl): ValidationErrors | null {
41 | const validNames = ['Aretha', 'Ella', 'Etta', 'Nina'];
42 |
43 | const isValid = validNames
44 | .map(n => c.value && c.value.indexOf(n) !== -1)
45 | .filter(v => v)
46 | .reduce((prev, curr) => true, false);
47 |
48 | if (!isValid) {
49 | return {
50 | validName: {
51 | validNames
52 | }
53 | };
54 | }
55 |
56 | return null;
57 | }
58 |
59 |
60 | describe('ValidationCollectorService', () => {
61 | beforeEach(() => {
62 | TestBed.configureTestingModule({
63 | providers: [
64 | ValidationCollectorService,
65 | {provide: NG_VALIDATORS, useValue: validName, multi: true},
66 | {provide: NG_ASYNC_VALIDATORS, useValue: isBlacklistedName, multi: true}
67 | ]
68 | });
69 | });
70 |
71 |
72 | it('should be created', inject([ValidationCollectorService], (service: ValidationCollectorService) => {
73 | expect(service).toBeTruthy();
74 | }));
75 |
76 | describe('getCustomValidatorFn', () => {
77 |
78 | it('should throw if we pass in no validator name', inject([ValidationCollectorService], (service: ValidationCollectorService) => {
79 | const functionName = undefined;
80 | expect(() => {
81 | service.getCustomValidatorFn(functionName);
82 | }).toThrow(new Error('No validation name given to search for'));
83 | }));
84 |
85 | it('should return the function if we request a custom existing function',
86 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
87 | const validNameError = {
88 | validName: {
89 | validNames: ['Aretha', 'Ella', 'Etta', 'Nina']
90 | }
91 | };
92 | const customValidatorFunction = service.getCustomValidatorFn('validName');
93 | expect(typeof customValidatorFunction).toBe('function');
94 | const fc: FormControl = new FormControl('');
95 | expect(customValidatorFunction(fc).validName).toEqual(validNameError.validName);
96 |
97 | fc.setValue('Ella');
98 | expect(customValidatorFunction(fc)).toEqual(null);
99 | }));
100 |
101 | });
102 |
103 | describe('getValidatorFn', () => {
104 |
105 | it('should throw if we pass in no validator name', inject([ValidationCollectorService], (service: ValidationCollectorService) => {
106 | const functionName = undefined;
107 | expect(() => {
108 | service.getValidatorFn(functionName);
109 | }).toThrow(new Error('No validation name given to search for'));
110 | }));
111 |
112 | it('should throw if we request a not existing validator',
113 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
114 | const functionName = 'notExistingFunction';
115 | expect(() => {
116 | service.getValidatorFn(functionName);
117 | }).toThrow(new Error(`Validator "${functionName}" is not provided via NG_VALIDATORS`));
118 | }));
119 |
120 | it('should throw if we pass in params for a validator with no params',
121 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
122 | const functionName = 'required';
123 | expect(() => {
124 | service.getValidatorFn(functionName, ['some', 'params']);
125 | }).toThrow(new Error(`Validator "${functionName}" is not provided a function.
126 | Did you provide params for a validator that don't need them?`));
127 | }));
128 |
129 | it('should be able to get the built in validator',
130 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
131 | const buildInValidatorNames: string[] = [
132 | 'required',
133 | 'minLength',
134 | 'maxLength',
135 | 'min',
136 | 'max',
137 | 'pattern',
138 | 'email',
139 | 'nullValidator',
140 | 'requiredTrue'
141 | ];
142 | for (let i = 0; i < buildInValidatorNames.length; i++) {
143 | const func: ValidatorFn = service.getValidatorFn(buildInValidatorNames[i]);
144 | expect(typeof func).toBe('function');
145 | }
146 | }));
147 |
148 | it('Built in validator without params should work',
149 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
150 | const requiredError = {required: true};
151 | const func: ValidatorFn = service.getValidatorFn('required');
152 | const fc: FormControl = new FormControl('');
153 | expect(func(fc)).toEqual(requiredError);
154 | fc.setValue('42');
155 | expect(func(fc)).toEqual(null);
156 | }));
157 |
158 | it('built in validator with params should work',
159 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
160 | const minlengthError = {
161 | minLength: {
162 | requiredLength: 3,
163 | actualLength: 2
164 | }
165 | };
166 | const func: ValidatorFn = service.getValidatorFn('minLength', [3]);
167 |
168 | const fc: FormControl = new FormControl('');
169 | expect(func(fc)).toEqual(null);
170 |
171 | fc.setValue('42');
172 | expect(func(fc).minlength).toEqual(minlengthError.minLength);
173 |
174 | fc.setValue('108');
175 | expect(func(fc)).toEqual(null);
176 | }));
177 |
178 | it('should return the function if we request a custom existing function',
179 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
180 | const validNameError = {
181 | validName: {
182 | validNames: ['Aretha', 'Ella', 'Etta', 'Nina']
183 | }
184 | };
185 | const customValidatorFunction = service.getValidatorFn('validName');
186 | expect(typeof customValidatorFunction).toBe('function');
187 | const fc: FormControl = new FormControl('');
188 | expect(customValidatorFunction(fc).validName).toEqual(validNameError.validName);
189 |
190 | fc.setValue('Ella');
191 | expect(customValidatorFunction(fc)).toEqual(null);
192 | }));
193 |
194 | });
195 |
196 | describe('getValidators', () => {
197 |
198 | it('should throw if we pass in no validator name', inject([ValidationCollectorService], (service: ValidationCollectorService) => {
199 | const config = [{name: undefined}];
200 | expect(() => {
201 | service.getValidators(config);
202 | }).toThrow(new Error('No validation name given to search for'));
203 | }));
204 |
205 | it('should throw if we request a not existing validator ',
206 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
207 | const functionName = 'notExistingFunction';
208 | expect(() => {
209 | service.getValidatorFn(functionName);
210 | }).toThrow(new Error(`Validator "${functionName}" is not provided via NG_VALIDATORS`));
211 | }));
212 |
213 | it('should throw if we pass in params for a validator with no params',
214 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
215 | const config: IValidatorConfig[] = [
216 | {name: 'required', params: ['some', 'params']}
217 | ];
218 |
219 | expect(() => {
220 | service.getValidators(config);
221 | }).toThrow(new Error(`Validator "${config[0].name}" is not provided a function.
222 | Did you provide params for a validator that don't need them?`));
223 | }));
224 |
225 | it('should be able to get the built in validator',
226 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
227 | const buildInValidatorConfig: IValidatorConfig[] = [
228 | {name: 'required'},
229 | {name: 'minLength', params: [3]},
230 | {name: 'maxLength', params: [3]},
231 | {name: 'min', params: [3]},
232 | {name: 'max', params: [3]},
233 | {name: 'pattern', params: [/[a-z]/g]},
234 | {name: 'email'},
235 | {name: 'nullValidator'},
236 | {name: 'requiredTrue'}
237 | ];
238 |
239 | const funcs: ValidatorFn[] = service.getValidators(buildInValidatorConfig);
240 | expect(typeof funcs).toBe('object');
241 | expect(funcs.length).toBe(9);
242 |
243 | }));
244 |
245 | it('built in validator without params should work',
246 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
247 | const config: IValidatorConfig[] = [{name: 'required'}];
248 | const requiredError = {required: true};
249 | const funcs: ValidatorFn[] = service.getValidators(config);
250 | expect(typeof funcs).toBe('object');
251 | expect(funcs.length).toBe(1);
252 |
253 | const fc: FormControl = new FormControl('');
254 | expect(funcs[0](fc)).toEqual(requiredError);
255 | fc.setValue('42');
256 | expect(funcs[0](fc)).toEqual(null);
257 | }));
258 |
259 | it('built in validator with params should work',
260 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
261 | const config: IValidatorConfig[] = [{name: 'minLength', params: [3]}];
262 | const minlengthError = {
263 | minLength: {
264 | requiredLength: 3,
265 | actualLength: 2
266 | }
267 | };
268 | const funcs: ValidatorFn[] = service.getValidators(config);
269 | expect(typeof funcs).toBe('object');
270 | expect(funcs.length).toBe(1);
271 |
272 | const fc: FormControl = new FormControl('');
273 | expect(funcs[0](fc)).toEqual(null);
274 |
275 | fc.setValue('42');
276 | expect(funcs[0](fc).minlength).toEqual(minlengthError.minLength);
277 |
278 | fc.setValue('108');
279 | expect(funcs[0](fc)).toEqual(null);
280 | }));
281 |
282 | it('should return the function if we request a custom existing function',
283 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
284 | const config: IValidatorConfig[] = [{name: 'validName'}];
285 | const customValidatorFunctions = service.getValidators(config);
286 | expect(typeof customValidatorFunctions).toBe('object');
287 | expect(customValidatorFunctions.length).toBe(1);
288 | }));
289 |
290 | it('custom validator without params should work',
291 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
292 | const config: IValidatorConfig[] = [{name: 'validName'}];
293 | const validNameError = {
294 | validName: {
295 | validNames: ['Aretha', 'Ella', 'Etta', 'Nina']
296 | }
297 | };
298 | const customValidatorFunctions = service.getValidators(config);
299 |
300 | const fc: FormControl = new FormControl('');
301 | expect(customValidatorFunctions[0](fc).validName).toEqual(validNameError.validName);
302 |
303 | fc.setValue('Nina');
304 | expect(customValidatorFunctions[0](fc)).toEqual(null);
305 | }));
306 |
307 | xit('custom validator with params should work', () => {
308 |
309 | });
310 |
311 | });
312 |
313 | describe('getCustomAsyncValidatorFn', () => {
314 |
315 | it('should throw if we pass in no asyncvalidator name',
316 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
317 | const functionName = undefined;
318 | expect(() => {
319 | service.getCustomAsyncValidatorFn(functionName);
320 | }).toThrow(new Error('No asyncvalidation name given to search for'));
321 | }));
322 |
323 | it('should throw if we request a not existing asyncvalidator',
324 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
325 | const functionName = 'notExistingFunction';
326 | expect(() => {
327 | service.getCustomAsyncValidatorFn(functionName);
328 | }).toThrow(new Error(`Asyncvalidator "${functionName}" is not provided via NG_ASYNC_VALIDATORS`));
329 | }));
330 |
331 | it('should throw if we pass in params for a asyncvalidator with no params',
332 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
333 | const functionName = 'isBlacklistedName';
334 | expect(() => {
335 | service.getCustomAsyncValidatorFn(functionName, ['some', 'params']);
336 | }).toThrow(new Error(`Asyncvalidator "${functionName}" is not provided a function.
337 | Did you provide params for a validator that don't need them?`));
338 | }));
339 |
340 | it('custom async validator without params should work',
341 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
342 | const isBlacklistedNameError = {
343 | isBlacklistedName: {
344 | actual: 'wxy',
345 | mandatoryChars: 'abcde'
346 | }
347 | };
348 | const func: ValidatorFn = service.getCustomAsyncValidatorFn('isBlacklistedName');
349 |
350 | const fc: FormControl = new FormControl('');
351 | func(fc).subscribe((n) => {
352 | expect(n).toEqual(null);
353 | });
354 |
355 | fc.setValue('wxy');
356 | func(fc).subscribe((n) => {
357 | expect(n.isBlacklistedName).toEqual(isBlacklistedNameError.isBlacklistedName);
358 | });
359 |
360 | fc.setValue('b');
361 | func(fc).subscribe((n) => {
362 | expect(n).toEqual(null);
363 | });
364 | }));
365 |
366 | xit('custom async validator with params should work', () => {
367 |
368 | });
369 |
370 | });
371 |
372 | describe('getAsyncValidators', () => {
373 |
374 | it('should throw if we pass in no asyncvalidator name', inject([ValidationCollectorService], (service: ValidationCollectorService) => {
375 | const config = [{name: undefined}];
376 | expect(() => {
377 | service.getAsyncValidators(config);
378 | }).toThrow(new Error('No asyncvalidation name given to search for'));
379 | }));
380 |
381 | it('should throw if we request a not existing validator ',
382 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
383 | const config = [{name: 'notExistingFunction'}];
384 | expect(() => {
385 | service.getAsyncValidators(config);
386 | }).toThrow(new Error(`Asyncvalidator "${config[0].name}" is not provided via NG_ASYNC_VALIDATORS`));
387 | }));
388 |
389 | it('should throw if we pass in params for a validator with no params',
390 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
391 | const config: IValidatorConfig[] = [
392 | {name: 'isBlacklistedName', params: ['some', 'params']}
393 | ];
394 |
395 | expect(() => {
396 | service.getAsyncValidators(config);
397 | }).toThrow(new Error(`Asyncvalidator "${config[0].name}" is not provided a function.
398 | Did you provide params for a validator that don't need them?`));
399 | }));
400 |
401 | it('should return the function if we request a custom existing asyncvalidator',
402 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
403 | const config: IValidatorConfig[] = [{name: 'isBlacklistedName'}];
404 | const customAsyncValidatorFunctions = service.getAsyncValidators(config);
405 | expect(typeof customAsyncValidatorFunctions).toBe('object');
406 | expect(customAsyncValidatorFunctions.length).toBe(1);
407 | }));
408 |
409 | it('custom asyncvalidator without params should work',
410 | inject([ValidationCollectorService], (service: ValidationCollectorService) => {
411 | const config: IValidatorConfig[] = [{name: 'isBlacklistedName'}];
412 | const isBlacklistedNameError = {
413 | isBlacklistedName: {
414 | actual: '',
415 | mandatoryChars: 'abcde'
416 | }
417 | };
418 | const customAsyncValidatorFunctions: any[] = service.getAsyncValidators(config);
419 |
420 | const fc: FormControl = new FormControl('');
421 | customAsyncValidatorFunctions[0](fc).subscribe((n) => {
422 | expect(n.isBlacklistedName).toEqual(isBlacklistedNameError.isBlacklistedName);
423 | });
424 |
425 | fc.setValue('cd');
426 | customAsyncValidatorFunctions[0](fc).subscribe((n) => {
427 | expect(n).toEqual(null);
428 | });
429 |
430 | })
431 | );
432 |
433 | xit('custom asyncvalidator with params should work', () => {
434 |
435 | });
436 |
437 | })
438 | ;
439 |
440 | })
441 | ;
442 |
--------------------------------------------------------------------------------
/tools/gulp/inline-resources.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // https://github.com/filipesilva/angular-quickstart-lib/blob/master/inline-resources.js
3 | 'use strict';
4 |
5 | const fs = require('fs');
6 | const path = require('path');
7 | const glob = require('glob');
8 | const sass = require('node-sass');
9 | const tildeImporter = require('node-sass-tilde-importer');
10 |
11 | /**
12 | * Simple Promiseify function that takes a Node API and return a version that supports promises.
13 | * We use promises instead of synchronized functions to make the process less I/O bound and
14 | * faster. It also simplifies the code.
15 | */
16 | function promiseify(fn) {
17 | return function () {
18 | const args = [].slice.call(arguments, 0);
19 | return new Promise((resolve, reject) => {
20 | fn.apply(this, args.concat([function (err, value) {
21 | if (err) {
22 | reject(err);
23 | } else {
24 | resolve(value);
25 | }
26 | }]));
27 | });
28 | };
29 | }
30 |
31 | const readFile = promiseify(fs.readFile);
32 | const writeFile = promiseify(fs.writeFile);
33 |
34 | /**
35 | * Inline resources in a tsc/ngc compilation.
36 | * @param projectPath {string} Path to the project.
37 | */
38 | function inlineResources(projectPath) {
39 |
40 | // Match only TypeScript files in projectPath.
41 | const files = glob.sync('**/*.ts', {cwd: projectPath});
42 |
43 | // For each file, inline the templates and styles under it and write the new file.
44 | return Promise.all(files.map(filePath => {
45 | const fullFilePath = path.join(projectPath, filePath);
46 | return readFile(fullFilePath, 'utf-8')
47 | .then(content => inlineResourcesFromString(content, url => {
48 | // Resolve the template url.
49 | return path.join(path.dirname(fullFilePath), url);
50 | }))
51 | .then(content => writeFile(fullFilePath, content))
52 | .catch(err => {
53 | console.error('An error occured: ', err);
54 | });
55 | }));
56 | }
57 |
58 | /**
59 | * Inline resources from a string content.
60 | * @param content {string} The source file's content.
61 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
62 | * @returns {string} The content with resources inlined.
63 | */
64 | function inlineResourcesFromString(content, urlResolver) {
65 | // Curry through the inlining functions.
66 | return [
67 | inlineTemplate,
68 | inlineStyle,
69 | removeModuleId
70 | ].reduce((content, fn) => fn(content, urlResolver), content);
71 | }
72 |
73 | /**
74 | * Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and
75 | * replace with `template: ...` (with the content of the file included).
76 | * @param content {string} The source file's content.
77 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
78 | * @return {string} The content with all templates inlined.
79 | */
80 | function inlineTemplate(content, urlResolver) {
81 | return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) {
82 | const templateFile = urlResolver(templateUrl);
83 | const templateContent = fs.readFileSync(templateFile, 'utf-8');
84 | const shortenedTemplate = templateContent
85 | .replace(/([\n\r]\s*)+/gm, ' ')
86 | .replace(/"/g, '\\"');
87 | return `template: "${shortenedTemplate}"`;
88 | });
89 | }
90 |
91 |
92 | /**
93 | * Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and
94 | * replace with `styles: [...]` (with the content of the file included).
95 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
96 | * @param content {string} The source file's content.
97 | * @return {string} The content with all styles inlined.
98 | */
99 | function inlineStyle(content, urlResolver) {
100 | return content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) {
101 | const urls = eval(styleUrls);
102 | return 'styles: ['
103 | + urls.map(styleUrl => {
104 | const styleFile = urlResolver(styleUrl);
105 | const originContent = fs.readFileSync(styleFile, 'utf-8');
106 | const styleContent = styleFile.endsWith('.scss') ? buildSass(originContent, styleFile) : originContent;
107 | const shortenedStyle = styleContent
108 | .replace(/([\n\r]\s*)+/gm, ' ')
109 | .replace(/"/g, '\\"');
110 | return `"${shortenedStyle}"`;
111 | })
112 | .join(',\n')
113 | + ']';
114 | });
115 | }
116 |
117 | /**
118 | * build sass content to css
119 | * @param content {string} the css content
120 | * @param sourceFile {string} the scss file sourceFile
121 | * @return {string} the generated css, empty string if error occured
122 | */
123 | function buildSass(content, sourceFile) {
124 | try {
125 | const result = sass.renderSync({
126 | data: content,
127 | file: sourceFile,
128 | importer: tildeImporter
129 | });
130 | return result.css.toString()
131 | } catch (e) {
132 | console.error('\x1b[41m');
133 | console.error('at ' + sourceFile + ':' + e.line + ":" + e.column);
134 | console.error(e.formatted);
135 | console.error('\x1b[0m');
136 | return "";
137 | }
138 | }
139 |
140 | /**
141 | * Remove every mention of `moduleId: module.id`.
142 | * @param content {string} The source file's content.
143 | * @returns {string} The content with all moduleId: mentions removed.
144 | */
145 | function removeModuleId(content) {
146 | return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, '');
147 | }
148 |
149 | module.exports = inlineResources;
150 | module.exports.inlineResourcesFromString = inlineResourcesFromString;
151 |
152 | // Run inlineResources if module is being called directly from the CLI with arguments.
153 | if (require.main === module && process.argv.length > 2) {
154 | console.log('Inlining resources from project:', process.argv[2]);
155 | return inlineResources(process.argv[2]);
156 | }
157 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "baseUrl": "src",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "moduleResolution": "node",
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "node_modules/@types"
14 | ],
15 | "lib": [
16 | "es2016",
17 | "dom"
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "eofline": true,
15 | "forin": true,
16 | "import-blacklist": [
17 | true,
18 | "rxjs"
19 | ],
20 | "import-spacing": true,
21 | "indent": [
22 | true,
23 | "spaces"
24 | ],
25 | "interface-over-type-literal": true,
26 | "label-position": true,
27 | "max-line-length": [
28 | true,
29 | 140
30 | ],
31 | "member-access": false,
32 | "member-ordering": [
33 | true,
34 | "static-before-instance",
35 | "variables-before-functions"
36 | ],
37 | "no-arg": true,
38 | "no-bitwise": true,
39 | "no-console": [
40 | true,
41 | "debug",
42 | "info",
43 | "time",
44 | "timeEnd",
45 | "trace"
46 | ],
47 | "no-construct": true,
48 | "no-debugger": true,
49 | "no-duplicate-super": true,
50 | "no-empty": false,
51 | "no-empty-interface": true,
52 | "no-eval": true,
53 | "no-inferrable-types": [
54 | true,
55 | "ignore-params"
56 | ],
57 | "no-misused-new": true,
58 | "no-non-null-assertion": true,
59 | "no-shadowed-variable": true,
60 | "no-string-literal": false,
61 | "no-string-throw": true,
62 | "no-switch-case-fall-through": true,
63 | "no-trailing-whitespace": true,
64 | "no-unnecessary-initializer": true,
65 | "no-unused-expression": true,
66 | "no-use-before-declare": true,
67 | "no-var-keyword": true,
68 | "object-literal-sort-keys": false,
69 | "one-line": [
70 | true,
71 | "check-open-brace",
72 | "check-catch",
73 | "check-else",
74 | "check-whitespace"
75 | ],
76 | "prefer-const": true,
77 | "quotemark": [
78 | true,
79 | "single"
80 | ],
81 | "radix": true,
82 | "semicolon": [
83 | true,
84 | "always"
85 | ],
86 | "triple-equals": [
87 | true,
88 | "allow-null-check"
89 | ],
90 | "typedef-whitespace": [
91 | true,
92 | {
93 | "call-signature": "nospace",
94 | "index-signature": "nospace",
95 | "parameter": "nospace",
96 | "property-declaration": "nospace",
97 | "variable-declaration": "nospace"
98 | }
99 | ],
100 | "typeof-compare": true,
101 | "unified-signatures": true,
102 | "variable-name": false,
103 | "whitespace": [
104 | true,
105 | "check-branch",
106 | "check-decl",
107 | "check-operator",
108 | "check-separator",
109 | "check-type"
110 | ],
111 | "directive-selector": [
112 | false,
113 | "attribute",
114 | "app",
115 | "camelCase"
116 | ],
117 | "component-selector": [
118 | false,
119 | "element",
120 | "app",
121 | "kebab-case"
122 | ],
123 | "use-input-property-decorator": true,
124 | "use-output-property-decorator": true,
125 | "use-host-property-decorator": false,
126 | "no-input-rename": false,
127 | "no-output-rename": true,
128 | "use-life-cycle-interface": true,
129 | "use-pipe-transform-interface": true,
130 | "component-class-suffix": true,
131 | "directive-class-suffix": true,
132 | "no-access-missing-member": true,
133 | "templates-use-public": true,
134 | "invoke-injectable": true
135 | }
136 | }
137 |
--------------------------------------------------------------------------------