├── .editorconfig ├── .gitattributes ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── copyright.code-snippets └── settings.json ├── LICENSE ├── README.md ├── bin ├── ng-ts-codegen.js ├── openapi-generator-cli-3.3.4.jar └── openapi-generator-cli-3.3.4.jar.sha1 ├── dist ├── baseformcontrol.factory.d.ts ├── formcontrol.d.ts ├── formgroup.d.ts ├── index.d.ts ├── index.es2015.js ├── index.esm.js └── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup-es2015.config.js ├── rollup.config.js ├── src ├── baseformcontrol.factory.spec.ts ├── baseformcontrol.factory.ts ├── formcontrol.spec.ts ├── formcontrol.ts ├── formgroup.spec.ts ├── formgroup.ts ├── index.ts └── mustache │ ├── api.module.mustache │ ├── api.service.mustache │ ├── apiCallByPartialMap.mustache │ ├── apiParamValidatorImports.mustache │ ├── apiPartialMap.mustache │ ├── configuration.mustache │ ├── licenseInfo.mustache │ ├── model.mustache │ ├── modelGeneric.mustache │ ├── modelGenericEnums.mustache │ ├── modelGenericValidators.mustache │ └── modelValidatorImports.mustache ├── tsconfig-es2015.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = off 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # treat following files as binary 2 | package-lock.json binary 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,jspm,sass,node,linux,angular,windows,visualstudiocode 2 | # Edit at https://www.gitignore.io/?templates=osx,jspm,sass,node,linux,angular,windows,visualstudiocode 3 | 4 | ### Angular ### 5 | ## Angular ## 6 | # compiled output 7 | /.rpt2_cache 8 | /tmp 9 | /app/**/*.js 10 | /app/**/*.js.map 11 | /lib 12 | 13 | # dependencies 14 | /node_modules 15 | /bower_components 16 | 17 | # IDEs and editors 18 | /.idea 19 | 20 | # misc 21 | /.sass-cache 22 | /connect.lock 23 | /coverage/* 24 | /reports 25 | /libpeerconnection.log 26 | npm-debug.log 27 | testem.log 28 | /typings 29 | 30 | #System Files 31 | .DS_Store 32 | 33 | ### jspm ### 34 | jspm_packages 35 | 36 | ### Linux ### 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | ### Node ### 52 | # Logs 53 | logs 54 | *.log 55 | npm-debug.log* 56 | yarn-debug.log* 57 | yarn-error.log* 58 | 59 | # Runtime data 60 | pids 61 | *.pid 62 | *.seed 63 | *.pid.lock 64 | 65 | # Directory for instrumented libs generated by jscoverage/JSCover 66 | lib-cov 67 | 68 | # Coverage directory used by tools like istanbul 69 | coverage 70 | 71 | # nyc test coverage 72 | .nyc_output 73 | 74 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 75 | .grunt 76 | 77 | # Bower dependency directory (https://bower.io/) 78 | bower_components 79 | 80 | # node-waf configuration 81 | .lock-wscript 82 | 83 | # Compiled binary addons (https://nodejs.org/api/addons.html) 84 | build/Release 85 | 86 | # Dependency directories 87 | node_modules/ 88 | jspm_packages/ 89 | 90 | # TypeScript v1 declaration files 91 | typings/ 92 | 93 | # Optional npm cache directory 94 | .npm 95 | 96 | # Optional eslint cache 97 | .eslintcache 98 | 99 | # Optional REPL history 100 | .node_repl_history 101 | 102 | # Output of 'npm pack' 103 | *.tgz 104 | 105 | # Yarn Integrity file 106 | .yarn-integrity 107 | 108 | # dotenv environment variables file 109 | .env 110 | 111 | # parcel-bundler cache (https://parceljs.org/) 112 | .cache 113 | 114 | # next.js build output 115 | .next 116 | 117 | # nuxt.js build output 118 | .nuxt 119 | 120 | # vuepress build output 121 | .vuepress/dist 122 | 123 | # Serverless directories 124 | .serverless 125 | 126 | # FuseBox cache 127 | .fusebox/ 128 | 129 | ### OSX ### 130 | # General 131 | .AppleDouble 132 | .LSOverride 133 | 134 | # Icon must end with two \r 135 | Icon 136 | 137 | # Thumbnails 138 | ._* 139 | 140 | # Files that might appear in the root of a volume 141 | .DocumentRevisions-V100 142 | .fseventsd 143 | .Spotlight-V100 144 | .TemporaryItems 145 | .Trashes 146 | .VolumeIcon.icns 147 | .com.apple.timemachine.donotpresent 148 | 149 | # Directories potentially created on remote AFP share 150 | .AppleDB 151 | .AppleDesktop 152 | Network Trash Folder 153 | Temporary Items 154 | .apdisk 155 | 156 | ### Sass ### 157 | .sass-cache/ 158 | *.css.map 159 | *.sass.map 160 | *.scss.map 161 | 162 | ### VisualStudioCode ### 163 | .vscode/* 164 | !.vscode/settings.json 165 | !.vscode/tasks.json 166 | !.vscode/launch.json 167 | !.vscode/extensions.json 168 | !.vscode/*.code-snippets 169 | 170 | ### VisualStudioCode Patch ### 171 | # Ignore all local history of files 172 | .history 173 | 174 | ### Windows ### 175 | # Windows thumbnail cache files 176 | Thumbs.db 177 | ehthumbs.db 178 | ehthumbs_vista.db 179 | 180 | # Dump file 181 | *.stackdump 182 | 183 | # Folder config file 184 | [Dd]esktop.ini 185 | 186 | # Recycle Bin used on file shares 187 | $RECYCLE.BIN/ 188 | 189 | # Windows Installer files 190 | *.cab 191 | *.msi 192 | *.msix 193 | *.msm 194 | *.msp 195 | 196 | # Windows shortcuts 197 | *.lnk 198 | 199 | # End of https://www.gitignore.io/api/osx,jspm,sass,node,linux,angular,windows,visualstudiocode 200 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid", 10 | "htmlWhitespaceSensitivity": "css", 11 | "endOfLine": "lf" 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/copyright.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "copyright": { 3 | "prefix": "copyright", 4 | "body": [ 5 | "/*", 6 | " * Copyright(c) 1995 - $CURRENT_YEAR T-Systems Multimedia Solutions GmbH", 7 | " * Riesaer Str. 5, 01129 Dresden", 8 | " * All rights reserved.", 9 | " */" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "editor.rulers": [120], 4 | "editor.formatOnSave": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 T-Systems Multimedia Solutions GmbH (https://www.t-systems-mms.com/) 190 | Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) 191 | Copyright 2018 SmartBear Software 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI Angular Typescript Generator 2 | 3 | This is a code generator to generate angular-specific typescript code from an [OpenAPI-specification](https://www.openapis.org/). 4 | 5 | It's based on the [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator) from OpenAPITools. As opposed to the OpenAPI Generator which just generates models and services, the OpenAPI Angular Typescript Generator also generates the according validators and FormControlFactories to use with Angular's Reactive Forms. 6 | 7 | ## Usage 8 | 9 | Install it with `npm install --save openapi-typescript-angular-generator`. 10 | 11 | ### Code Generation 12 | 13 | Run it with `npx openapi-typescript-angular-generator` and following options: 14 | 15 | - `-i` file or URL of the openapi-specification 16 | - `-o` output destination for the generated code 17 | - `-e` (optional) building environment: java (default) or docker 18 | - `-m` (optional) mount root for the docker container 19 | - `-a` (optional) adds authorization headers when fetching the OpenAPI definitions 20 | remotely. Pass in a URL-encoded string of name:header with a comma 21 | separating multiple values 22 | - `--additional-properties` (optional) additional properties to pass to openapi-generator 23 | 24 | `Docker` or `Java` must be installed for the generator to work. 25 | 26 | Pay attention to the input/output-parameter when using docker. These parameters will be used after the docker container is started and the volume is mounted. So any path-resolution, except the input-parameter is an url, will be start from `/local`. 27 | 28 | Proxy settings from the executed process will be applied to the openapi-generator-process. 29 | 30 | ### Usage of custom `FormGroup` and `FormControl` with `FormControl`-factories 31 | 32 | For each model a `FormControl`-factory is generated. This factory can be used to create `FormControl`-instances: 33 | 34 | ``` 35 | // generated model 36 | interface MyModel { 37 | value: string; 38 | } 39 | namespace MyModel { 40 | export enum Properties { 41 | value = 'value', 42 | } 43 | } 44 | 45 | // example data that could be fetched from a service 46 | this.model: MyModel = { value: 'foobar' }; 47 | this.properties = MyModel.Properties; 48 | this.errorMap = { 49 | 'prefix.value.required': 'Value is required', 50 | }; 51 | 52 | // create a factory to create FormControls 53 | const factory = new MyModel.FormControlFactory(model); 54 | this.formGroup = new TypedFormGroup({ 55 | value: factory.createFormControl('value') 56 | }); 57 | ``` 58 | 59 | Now the created controls can be used at the view like this: 60 | 61 | ``` 62 |
63 | 64 | 66 | 67 | 68 | 69 | 70 | {{ errorMap[formGroup.nextControlErrorKey(properties.value, 'prefix')] }} 71 | 72 | 73 |
74 | ``` 75 | 76 | Only a single `span` is used to display errors for the field. Best usage is reached with `` tag from [material.angular.io](https://material.angular.io). 77 | 78 | ## License 79 | 80 | Copyright 2018 T-Systems Multimedia Solutions GmbH (https://www.t-systems-mms.com/)
81 | Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
82 | Copyright 2018 SmartBear Software 83 | 84 | Licensed under the Apache License, Version 2.0 (the "License"); 85 | you may not use this file except in compliance with the License. 86 | You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 87 | 88 | Unless required by applicable law or agreed to in writing, software 89 | distributed under the License is distributed on an "AS IS" BASIS, 90 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 91 | See the License for the specific language governing permissions and 92 | limitations under the License. 93 | -------------------------------------------------------------------------------- /bin/ng-ts-codegen.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH 5 | * Riesaer Str. 5, 01129 Dresden 6 | * All rights reserved. 7 | */ 8 | 9 | const argv = require('yargs').argv; 10 | const fse = require('fs-extra'); 11 | const { exec } = require('child_process'); 12 | const { resolve } = require('path'); 13 | const url = require('url'); 14 | 15 | const openApiVersion = '3.3.4'; 16 | const jarFileName = `openapi-generator-cli-${openApiVersion}.jar`; 17 | const dockerImageName = `openapitools/openapi-generator-cli:v${openApiVersion}`; 18 | 19 | /** 20 | * Converts proxy settings into java runtime parameters. 21 | */ 22 | function getProxyArgsForJava() { 23 | let proxyArgs = ''; 24 | if (process.env.HTTP_PROXY) { 25 | const parsedUrl = url.parse(process.env.HTTP_PROXY); 26 | proxyArgs = proxyArgs.concat(` -Dhttp.proxyHost=${parsedUrl.hostname} -Dhttp.proxyPort=${parsedUrl.port}`); 27 | } 28 | if (process.env.HTTPS_PROXY) { 29 | const parsedUrl = url.parse(process.env.HTTPS_PROXY); 30 | proxyArgs = proxyArgs.concat(` -Dhttps.proxyHost=${parsedUrl.hostname} -Dhttps.proxyPort=${parsedUrl.port}`); 31 | } 32 | if (process.env.NO_PROXY) { 33 | const noProxyValue = process.env.NO_PROXY.split(',').join('|'); 34 | proxyArgs = proxyArgs.concat(` -Dhttp.nonProxyHosts="${noProxyValue}" -Dhttps.nonProxyHosts="${noProxyValue}"`); 35 | } 36 | 37 | return proxyArgs; 38 | } 39 | 40 | /** 41 | * Converts proxy settings into docker run parameters. 42 | */ 43 | function getProxyArgsForDocker() { 44 | let proxyArgs = ''; 45 | if (process.env.HTTP_PROXY) { 46 | proxyArgs = proxyArgs.concat(` --env HTTP_PROXY="${process.env.HTTP_PROXY}"`); 47 | } 48 | if (process.env.HTTPS_PROXY) { 49 | proxyArgs = proxyArgs.concat(` --env HTTPS_PROXY="${process.env.HTTPS_PROXY}"`); 50 | } 51 | if (process.env.NO_PROXY) { 52 | proxyArgs = proxyArgs.concat(` --env NO_PROXY="${process.env.NO_PROXY}"`); 53 | } 54 | 55 | return proxyArgs; 56 | } 57 | 58 | /** 59 | * Adds the new given additional property to the given map of additional properties. 60 | * @param {Object} additionalProperties map of additional properties 61 | * @param {string} arg additional property to add 62 | */ 63 | function addAdditionalProperty(additionalProperties, arg) { 64 | if (typeof arg !== 'string') { 65 | return; 66 | } 67 | const addArg = arg.split('=', 2); 68 | additionalProperties[addArg[0]] = addArg[1]; 69 | } 70 | 71 | // Usage 72 | if (argv.help || argv.h) { 73 | console.log('[Usage]'); 74 | console.log( 75 | 'openapi-typescript-angular-generator -i -o [-e ] [-m ] [-a ] [--additional-properties ...]' 76 | ); 77 | process.exit(0); 78 | } 79 | 80 | // input and output must be defined 81 | if (!argv.i) { 82 | console.log('Please provide openapi-file: -i '); 83 | process.exit(1); 84 | } 85 | 86 | if (!argv.o) { 87 | console.log('Please provide output-directory: -o '); 88 | process.exit(1); 89 | } 90 | 91 | // define the actual command 92 | let command; 93 | let isDocker = false; 94 | if (argv.e === 'docker') { 95 | // transfer post processing env-variable to docker if it exists 96 | let envArgs = process.env.TS_POST_PROCESS_FILE ? ` --env TS_POST_PROCESS_FILE="${process.env.TS_POST_PROCESS_FILE}"` : '' 97 | 98 | // add proxy args 99 | envArgs += getProxyArgsForDocker(); 100 | 101 | const volume = argv.m || process.env.PWD; 102 | command = `docker run${envArgs} --rm -v ${volume}:/local ${dockerImageName}`; 103 | isDocker = true; 104 | } else { 105 | // default to java 106 | const proxyArgs = getProxyArgsForJava(); 107 | command = `java${proxyArgs} -jar ${resolve(__dirname, jarFileName)}`; 108 | } 109 | 110 | // join parameters to the command 111 | const isUrlInput = argv.i.startsWith('http://') || argv.i.startsWith('https://'); 112 | const args = [ 113 | 'generate', 114 | `-i ${isDocker && !isUrlInput ? `/local/${argv.i}` : argv.i}`, 115 | `-o ${isDocker ? `/local/${argv.o}` : argv.o}`, 116 | '-g=typescript-angular', 117 | `-t=${ 118 | isDocker 119 | ? '/local/node_modules/openapi-typescript-angular-generator/src/mustache' 120 | : resolve(__dirname, '../src/mustache') 121 | }`, 122 | ]; 123 | 124 | // enable post processing if environment variable TS_POST_PROCESS_FILE is set 125 | if (process.env.TS_POST_PROCESS_FILE) { 126 | args.push('--enable-post-process-file',) 127 | } 128 | 129 | // add auth headers 130 | if (argv.a) { 131 | args.push(`-a ${argv.a}`); 132 | } 133 | 134 | // additional properties 135 | const additionalProperties = { 136 | supportsES6: 'true', 137 | ngVersion: '7.0.0', 138 | modelPropertyNaming: 'original', 139 | }; 140 | if (argv['additional-properties']) { 141 | if (Array.isArray(argv['additional-properties'])) { 142 | argv['additional-properties'].forEach(arg => { 143 | addAdditionalProperty(additionalProperties, arg); 144 | }); 145 | } else { 146 | addAdditionalProperty(additionalProperties, argv['additional-properties']); 147 | } 148 | } 149 | for (const key in additionalProperties) { 150 | if (additionalProperties.hasOwnProperty(key)) { 151 | const element = additionalProperties[key]; 152 | args.push(`--additional-properties="${key}=${element}"`); 153 | } 154 | } 155 | 156 | // build command 157 | command += ` ${args.join(' ')}`; 158 | 159 | // execute 160 | console.log('Executing following command to generate the code:\n', command); 161 | const cmd = exec(command, () => { 162 | // clean up 163 | const files = ['.openapi-generator', '.gitignore', '.openapi-generator-ignore', 'git_push.sh', 'README.md']; 164 | files.forEach(f => fse.remove(resolve(process.cwd(), argv.o, f))); 165 | }); 166 | cmd.stdout.pipe(process.stdout); 167 | cmd.stderr.pipe(process.stderr); 168 | -------------------------------------------------------------------------------- /bin/openapi-generator-cli-3.3.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Systems-MMS/openapi-typescript-angular-generator/201d5f5153708672c18542354cb420751f6f1efa/bin/openapi-generator-cli-3.3.4.jar -------------------------------------------------------------------------------- /bin/openapi-generator-cli-3.3.4.jar.sha1: -------------------------------------------------------------------------------- 1 | 34c0bc3d6081d3bdbf946f6ac8d0213181642b98 openapi-generator-cli-3.3.4.jar 2 | -------------------------------------------------------------------------------- /dist/baseformcontrol.factory.d.ts: -------------------------------------------------------------------------------- 1 | import { ValidatorFn } from '@angular/forms'; 2 | import { TypedFormControl, TypedControlOptions } from './formcontrol'; 3 | /** 4 | * This is the base from control factory for each model. Based on this class, each model is implementing it's own 5 | * FormControlFactory. 6 | * Main purpose of this factory is to provide an easy way of creating form-controls with the correct validators. 7 | * The factory also ensures that model and validator match one another. 8 | */ 9 | export declare class BaseFormControlFactory { 10 | private map; 11 | /** 12 | * Constructor. 13 | * 14 | * @param model The model object. 15 | * @param validators properties validators map 16 | */ 17 | constructor(model: T, validators: { 18 | [K in keyof T]: [string, ValidatorFn][]; 19 | }); 20 | /** 21 | * Creates a new `TypedFormControl` instance. 22 | * 23 | * @param property the property of the model for which the `TypedFormControl` should be created. 24 | * @param controlOpts add custom validators to the default ones given in the constructor, optional async validators 25 | * and update mode. 26 | */ 27 | createFormControl(property: keyof T, controlOpts?: TypedControlOptions): TypedFormControl; 28 | } 29 | -------------------------------------------------------------------------------- /dist/formcontrol.d.ts: -------------------------------------------------------------------------------- 1 | import { FormControl, ValidatorFn, AsyncValidatorFn, FormControlStatus } from '@angular/forms'; 2 | import { Observable } from 'rxjs'; 3 | /** 4 | * Validator options to have mapping key -> validator function. 5 | */ 6 | export interface ValidatorOptions { 7 | /** mapping error key -> validator function */ 8 | validators: [string, ValidatorFn][]; 9 | /** mapping error key -> validator function */ 10 | asyncValidators?: [string, AsyncValidatorFn][]; 11 | } 12 | /** 13 | * Simplified options interface. 14 | */ 15 | export interface TypedControlOptions extends ValidatorOptions { 16 | /** updateOn */ 17 | updateOn?: 'change' | 'blur' | 'submit'; 18 | } 19 | /** @inheritdoc */ 20 | export declare class TypedFormControl extends FormControl { 21 | /** @inheritdoc */ 22 | readonly value: T; 23 | /** @inheritdoc */ 24 | readonly valueChanges: Observable; 25 | /** @inheritdoc */ 26 | readonly statusChanges: Observable; 27 | /** holds all possible validator names extracted by the given validators */ 28 | readonly registeredValidators: string[]; 29 | /** @inheritdoc */ 30 | constructor(formState?: T, opts?: TypedControlOptions); 31 | /** @inheritdoc */ 32 | patchValue(value: T, options?: { 33 | onlySelf?: boolean; 34 | emitEvent?: boolean; 35 | emitModelToViewChange?: boolean; 36 | emitViewToModelChange?: boolean; 37 | }): void; 38 | /** @inheritdoc */ 39 | setValue(value: T, options?: { 40 | onlySelf?: boolean; 41 | emitEvent?: boolean; 42 | emitModelToViewChange?: boolean; 43 | emitViewToModelChange?: boolean; 44 | }): void; 45 | /** @inheritdoc */ 46 | reset(formState?: T, options?: { 47 | onlySelf?: boolean; 48 | emitEvent?: boolean; 49 | }): void; 50 | /** 51 | * Sets new validators and updates possible error keys list. 52 | * 53 | * @param newValidators new validators 54 | */ 55 | setNewValidators(newValidators: ValidatorOptions): void; 56 | /** 57 | * Generates validator name list. 58 | * 59 | * @param validatorOpts options to handle 60 | */ 61 | private generateValidatorNames; 62 | } 63 | -------------------------------------------------------------------------------- /dist/formgroup.d.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, FormGroup, ValidatorFn, ValidationErrors } from '@angular/forms'; 2 | import { Observable } from 'rxjs'; 3 | /** 4 | * Typed FormGroup. 5 | */ 6 | export declare class TypedFormGroup extends FormGroup { 7 | /** @inheritdoc */ 8 | readonly value: T; 9 | /** @inheritdoc */ 10 | readonly valueChanges: Observable; 11 | /** holds a map with control names to possible validator names */ 12 | readonly registeredValidatorsMap: { 13 | [controlName in keyof T]: string[]; 14 | }; 15 | /** @inheritdoc */ 16 | constructor(controls: { 17 | [key in keyof T]: AbstractControl; 18 | }, validatorOrOpts?: ValidatorFn | Array | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | Array | null); 19 | /** @inheritdoc */ 20 | patchValue(value: Partial | T, options?: { 21 | onlySelf?: boolean; 22 | emitEvent?: boolean; 23 | }): void; 24 | /** @inheritdoc */ 25 | get(path: Array> | Extract): AbstractControl | null; 26 | /** 27 | * Returns group if available. 28 | * 29 | * @param path path to group 30 | */ 31 | getNestedGroup(path: Extract): TypedFormGroup | null; 32 | /** 33 | * Detects if an error is present for given control name. 34 | * 35 | * @param name control name of the form group 36 | */ 37 | hasControlErrors(name: Extract): boolean; 38 | /** 39 | * Detects if control has validator for given control name and validator name. 40 | * 41 | * @param name control name of the form group 42 | * @param validatorName validator name 43 | */ 44 | isValidatorRegistered(name: Extract, validatorName: string): boolean; 45 | /** 46 | * Returns an error key for the next error. 47 | * 48 | * @param name control key of the form group 49 | */ 50 | nextControlErrorKey(name: Extract): string; 51 | /** 52 | * Dispatches errors to this control and to child controls using given error map. 53 | * 54 | * @param errors error map 55 | * @param contextPath optional context path to errors set to 56 | */ 57 | dispatchErrors(errors: { 58 | [key: string]: ValidationErrors; 59 | }, contextPath?: string): void; 60 | } 61 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './baseformcontrol.factory'; 2 | export * from './formgroup'; 3 | export * from './formcontrol'; 4 | -------------------------------------------------------------------------------- /dist/index.es2015.js: -------------------------------------------------------------------------------- 1 | import { FormControl, FormGroup } from '@angular/forms'; 2 | 3 | /** @inheritdoc */ 4 | class TypedFormControl extends FormControl { 5 | /** @inheritdoc */ 6 | constructor(formState, opts) { 7 | super(formState, { 8 | validators: opts ? opts.validators.map(validator => validator && validator[1]) : null, 9 | asyncValidators: opts && opts.asyncValidators ? opts.asyncValidators.map(validator => validator && validator[1]) : null, 10 | updateOn: opts && opts.updateOn ? opts.updateOn : 'change', 11 | }); 12 | this.registeredValidators = this.generateValidatorNames(opts); 13 | } 14 | /** @inheritdoc */ 15 | patchValue(value, options) { 16 | super.patchValue(value, options); 17 | } 18 | /** @inheritdoc */ 19 | setValue(value, options) { 20 | super.setValue(value, options); 21 | } 22 | /** @inheritdoc */ 23 | reset(formState, options) { 24 | super.reset(formState, options); 25 | } 26 | /** 27 | * Sets new validators and updates possible error keys list. 28 | * 29 | * @param newValidators new validators 30 | */ 31 | setNewValidators(newValidators) { 32 | super.setValidators(newValidators ? newValidators.validators.map(validator => validator && validator[1]) : null); 33 | super.setAsyncValidators(newValidators && newValidators.asyncValidators 34 | ? newValidators.asyncValidators.map(validator => validator && validator[1]) 35 | : null); 36 | this.registeredValidators = this.generateValidatorNames(newValidators); 37 | } 38 | /** 39 | * Generates validator name list. 40 | * 41 | * @param validatorOpts options to handle 42 | */ 43 | generateValidatorNames(validatorOpts) { 44 | let keys = []; 45 | if (validatorOpts) { 46 | let validatorsList = [validatorOpts.validators]; 47 | if (validatorOpts.asyncValidators) { 48 | validatorsList.push(validatorOpts.asyncValidators); 49 | } 50 | validatorsList.forEach((validators) => { 51 | keys.push(...validators 52 | .map(validator => validator && validator[0]) 53 | // filter duplicates 54 | .filter((key, index, array) => array.indexOf(key) === index)); 55 | }); 56 | } 57 | return keys; 58 | } 59 | } 60 | 61 | /** 62 | * This is the base from control factory for each model. Based on this class, each model is implementing it's own 63 | * FormControlFactory. 64 | * Main purpose of this factory is to provide an easy way of creating form-controls with the correct validators. 65 | * The factory also ensures that model and validator match one another. 66 | */ 67 | class BaseFormControlFactory { 68 | /** 69 | * Constructor. 70 | * 71 | * @param model The model object. 72 | * @param validators properties validators map 73 | */ 74 | constructor(model, validators) { 75 | this.map = new Map(); 76 | for (const property in model) { 77 | if (!model.hasOwnProperty(property)) { 78 | continue; 79 | } 80 | this.map.set(property, { 81 | value: model[property], 82 | validators: validators[property] ? validators[property] : [], 83 | }); 84 | } 85 | } 86 | /** 87 | * Creates a new `TypedFormControl` instance. 88 | * 89 | * @param property the property of the model for which the `TypedFormControl` should be created. 90 | * @param controlOpts add custom validators to the default ones given in the constructor, optional async validators 91 | * and update mode. 92 | */ 93 | createFormControl(property, controlOpts) { 94 | const model = this.map.get(property); 95 | if (model) { 96 | return new TypedFormControl(model.value, { 97 | validators: [...model.validators, ...(controlOpts ? controlOpts.validators : [])], 98 | asyncValidators: controlOpts ? controlOpts.asyncValidators : undefined, 99 | updateOn: controlOpts ? controlOpts.updateOn : undefined, 100 | }); 101 | } 102 | return new TypedFormControl(); 103 | } 104 | } 105 | 106 | /** 107 | * Typed FormGroup. 108 | */ 109 | class TypedFormGroup extends FormGroup { 110 | /** @inheritdoc */ 111 | constructor(controls, validatorOrOpts, asyncValidator) { 112 | super(controls, validatorOrOpts, asyncValidator); 113 | const map = {}; 114 | Object.keys(controls).forEach(controlName => { 115 | const control = controls[controlName]; 116 | if (control instanceof TypedFormControl) { 117 | Object.defineProperty(map, controlName, { 118 | get: () => { 119 | return control.registeredValidators; 120 | }, 121 | }); 122 | } 123 | }); 124 | this.registeredValidatorsMap = map; 125 | } 126 | /** @inheritdoc */ 127 | patchValue(value, options) { 128 | super.patchValue(value, options); 129 | } 130 | /** @inheritdoc */ 131 | get(path) { 132 | return super.get(path); 133 | } 134 | /** 135 | * Returns group if available. 136 | * 137 | * @param path path to group 138 | */ 139 | getNestedGroup(path) { 140 | const control = this.get(path); 141 | if (control instanceof TypedFormGroup) { 142 | return control; 143 | } 144 | return null; 145 | } 146 | /** 147 | * Detects if an error is present for given control name. 148 | * 149 | * @param name control name of the form group 150 | */ 151 | hasControlErrors(name) { 152 | const control = this.get(name); 153 | return !!(control && control.errors); 154 | } 155 | /** 156 | * Detects if control has validator for given control name and validator name. 157 | * 158 | * @param name control name of the form group 159 | * @param validatorName validator name 160 | */ 161 | isValidatorRegistered(name, validatorName) { 162 | return (this.registeredValidatorsMap[name] && 163 | this.registeredValidatorsMap[name].some(errorKey => errorKey === validatorName)); 164 | } 165 | /** 166 | * Returns an error key for the next error. 167 | * 168 | * @param name control key of the form group 169 | */ 170 | nextControlErrorKey(name) { 171 | const control = this.get(name); 172 | if (control && control.errors) { 173 | // try client side keys first for correct order 174 | let error = this.registeredValidatorsMap[name] && 175 | this.registeredValidatorsMap[name].find(validatorKey => control.hasError(validatorKey)); 176 | if (!error) { 177 | // fallback to all errors including custom errors set after backend calls 178 | error = Object.keys(control.errors).shift(); 179 | } 180 | if (error) { 181 | return error; 182 | } 183 | } 184 | return ''; 185 | } 186 | /** 187 | * Dispatches errors to this control and to child controls using given error map. 188 | * 189 | * @param errors error map 190 | * @param contextPath optional context path to errors set to 191 | */ 192 | dispatchErrors(errors, contextPath) { 193 | const paths = Object.keys(errors); 194 | paths.forEach(path => { 195 | const control = this.get((contextPath ? `${contextPath}.${path}` : path)); 196 | if (control) { 197 | // enables showing errors in view 198 | control.enable(); 199 | control.markAsTouched(); 200 | control.setErrors(errors[path]); 201 | } 202 | }); 203 | } 204 | } 205 | 206 | export { BaseFormControlFactory, TypedFormControl, TypedFormGroup }; 207 | -------------------------------------------------------------------------------- /dist/index.esm.js: -------------------------------------------------------------------------------- 1 | import { FormControl, FormGroup } from '@angular/forms'; 2 | 3 | /** @inheritdoc */ 4 | class TypedFormControl extends FormControl { 5 | /** @inheritdoc */ 6 | constructor(formState, opts) { 7 | super(formState, { 8 | validators: opts ? opts.validators.map(validator => validator && validator[1]) : null, 9 | asyncValidators: opts && opts.asyncValidators ? opts.asyncValidators.map(validator => validator && validator[1]) : null, 10 | updateOn: opts && opts.updateOn ? opts.updateOn : 'change', 11 | }); 12 | this.registeredValidators = this.generateValidatorNames(opts); 13 | } 14 | /** @inheritdoc */ 15 | patchValue(value, options) { 16 | super.patchValue(value, options); 17 | } 18 | /** @inheritdoc */ 19 | setValue(value, options) { 20 | super.setValue(value, options); 21 | } 22 | /** @inheritdoc */ 23 | reset(formState, options) { 24 | super.reset(formState, options); 25 | } 26 | /** 27 | * Sets new validators and updates possible error keys list. 28 | * 29 | * @param newValidators new validators 30 | */ 31 | setNewValidators(newValidators) { 32 | super.setValidators(newValidators ? newValidators.validators.map(validator => validator && validator[1]) : null); 33 | super.setAsyncValidators(newValidators && newValidators.asyncValidators 34 | ? newValidators.asyncValidators.map(validator => validator && validator[1]) 35 | : null); 36 | this.registeredValidators = this.generateValidatorNames(newValidators); 37 | } 38 | /** 39 | * Generates validator name list. 40 | * 41 | * @param validatorOpts options to handle 42 | */ 43 | generateValidatorNames(validatorOpts) { 44 | let keys = []; 45 | if (validatorOpts) { 46 | let validatorsList = [validatorOpts.validators]; 47 | if (validatorOpts.asyncValidators) { 48 | validatorsList.push(validatorOpts.asyncValidators); 49 | } 50 | validatorsList.forEach((validators) => { 51 | keys.push(...validators 52 | .map(validator => validator && validator[0]) 53 | // filter duplicates 54 | .filter((key, index, array) => array.indexOf(key) === index)); 55 | }); 56 | } 57 | return keys; 58 | } 59 | } 60 | 61 | /** 62 | * This is the base from control factory for each model. Based on this class, each model is implementing it's own 63 | * FormControlFactory. 64 | * Main purpose of this factory is to provide an easy way of creating form-controls with the correct validators. 65 | * The factory also ensures that model and validator match one another. 66 | */ 67 | class BaseFormControlFactory { 68 | /** 69 | * Constructor. 70 | * 71 | * @param model The model object. 72 | * @param validators properties validators map 73 | */ 74 | constructor(model, validators) { 75 | this.map = new Map(); 76 | for (const property in model) { 77 | if (!model.hasOwnProperty(property)) { 78 | continue; 79 | } 80 | this.map.set(property, { 81 | value: model[property], 82 | validators: validators[property] ? validators[property] : [], 83 | }); 84 | } 85 | } 86 | /** 87 | * Creates a new `TypedFormControl` instance. 88 | * 89 | * @param property the property of the model for which the `TypedFormControl` should be created. 90 | * @param controlOpts add custom validators to the default ones given in the constructor, optional async validators 91 | * and update mode. 92 | */ 93 | createFormControl(property, controlOpts) { 94 | const model = this.map.get(property); 95 | if (model) { 96 | return new TypedFormControl(model.value, { 97 | validators: [...model.validators, ...(controlOpts ? controlOpts.validators : [])], 98 | asyncValidators: controlOpts ? controlOpts.asyncValidators : undefined, 99 | updateOn: controlOpts ? controlOpts.updateOn : undefined, 100 | }); 101 | } 102 | return new TypedFormControl(); 103 | } 104 | } 105 | 106 | /** 107 | * Typed FormGroup. 108 | */ 109 | class TypedFormGroup extends FormGroup { 110 | /** @inheritdoc */ 111 | constructor(controls, validatorOrOpts, asyncValidator) { 112 | super(controls, validatorOrOpts, asyncValidator); 113 | const map = {}; 114 | Object.keys(controls).forEach(controlName => { 115 | const control = controls[controlName]; 116 | if (control instanceof TypedFormControl) { 117 | Object.defineProperty(map, controlName, { 118 | get: () => { 119 | return control.registeredValidators; 120 | }, 121 | }); 122 | } 123 | }); 124 | this.registeredValidatorsMap = map; 125 | } 126 | /** @inheritdoc */ 127 | patchValue(value, options) { 128 | super.patchValue(value, options); 129 | } 130 | /** @inheritdoc */ 131 | get(path) { 132 | return super.get(path); 133 | } 134 | /** 135 | * Returns group if available. 136 | * 137 | * @param path path to group 138 | */ 139 | getNestedGroup(path) { 140 | const control = this.get(path); 141 | if (control instanceof TypedFormGroup) { 142 | return control; 143 | } 144 | return null; 145 | } 146 | /** 147 | * Detects if an error is present for given control name. 148 | * 149 | * @param name control name of the form group 150 | */ 151 | hasControlErrors(name) { 152 | const control = this.get(name); 153 | return !!(control && control.errors); 154 | } 155 | /** 156 | * Detects if control has validator for given control name and validator name. 157 | * 158 | * @param name control name of the form group 159 | * @param validatorName validator name 160 | */ 161 | isValidatorRegistered(name, validatorName) { 162 | return (this.registeredValidatorsMap[name] && 163 | this.registeredValidatorsMap[name].some(errorKey => errorKey === validatorName)); 164 | } 165 | /** 166 | * Returns an error key for the next error. 167 | * 168 | * @param name control key of the form group 169 | */ 170 | nextControlErrorKey(name) { 171 | const control = this.get(name); 172 | if (control && control.errors) { 173 | // try client side keys first for correct order 174 | let error = this.registeredValidatorsMap[name] && 175 | this.registeredValidatorsMap[name].find(validatorKey => control.hasError(validatorKey)); 176 | if (!error) { 177 | // fallback to all errors including custom errors set after backend calls 178 | error = Object.keys(control.errors).shift(); 179 | } 180 | if (error) { 181 | return error; 182 | } 183 | } 184 | return ''; 185 | } 186 | /** 187 | * Dispatches errors to this control and to child controls using given error map. 188 | * 189 | * @param errors error map 190 | * @param contextPath optional context path to errors set to 191 | */ 192 | dispatchErrors(errors, contextPath) { 193 | const paths = Object.keys(errors); 194 | paths.forEach(path => { 195 | const control = this.get((contextPath ? `${contextPath}.${path}` : path)); 196 | if (control) { 197 | // enables showing errors in view 198 | control.enable(); 199 | control.markAsTouched(); 200 | control.setErrors(errors[path]); 201 | } 202 | }); 203 | } 204 | } 205 | 206 | export { BaseFormControlFactory, TypedFormControl, TypedFormGroup }; 207 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var forms = require('@angular/forms'); 6 | 7 | /** @inheritdoc */ 8 | class TypedFormControl extends forms.FormControl { 9 | /** @inheritdoc */ 10 | constructor(formState, opts) { 11 | super(formState, { 12 | validators: opts ? opts.validators.map(validator => validator && validator[1]) : null, 13 | asyncValidators: opts && opts.asyncValidators ? opts.asyncValidators.map(validator => validator && validator[1]) : null, 14 | updateOn: opts && opts.updateOn ? opts.updateOn : 'change', 15 | }); 16 | this.registeredValidators = this.generateValidatorNames(opts); 17 | } 18 | /** @inheritdoc */ 19 | patchValue(value, options) { 20 | super.patchValue(value, options); 21 | } 22 | /** @inheritdoc */ 23 | setValue(value, options) { 24 | super.setValue(value, options); 25 | } 26 | /** @inheritdoc */ 27 | reset(formState, options) { 28 | super.reset(formState, options); 29 | } 30 | /** 31 | * Sets new validators and updates possible error keys list. 32 | * 33 | * @param newValidators new validators 34 | */ 35 | setNewValidators(newValidators) { 36 | super.setValidators(newValidators ? newValidators.validators.map(validator => validator && validator[1]) : null); 37 | super.setAsyncValidators(newValidators && newValidators.asyncValidators 38 | ? newValidators.asyncValidators.map(validator => validator && validator[1]) 39 | : null); 40 | this.registeredValidators = this.generateValidatorNames(newValidators); 41 | } 42 | /** 43 | * Generates validator name list. 44 | * 45 | * @param validatorOpts options to handle 46 | */ 47 | generateValidatorNames(validatorOpts) { 48 | let keys = []; 49 | if (validatorOpts) { 50 | let validatorsList = [validatorOpts.validators]; 51 | if (validatorOpts.asyncValidators) { 52 | validatorsList.push(validatorOpts.asyncValidators); 53 | } 54 | validatorsList.forEach((validators) => { 55 | keys.push(...validators 56 | .map(validator => validator && validator[0]) 57 | // filter duplicates 58 | .filter((key, index, array) => array.indexOf(key) === index)); 59 | }); 60 | } 61 | return keys; 62 | } 63 | } 64 | 65 | /** 66 | * This is the base from control factory for each model. Based on this class, each model is implementing it's own 67 | * FormControlFactory. 68 | * Main purpose of this factory is to provide an easy way of creating form-controls with the correct validators. 69 | * The factory also ensures that model and validator match one another. 70 | */ 71 | class BaseFormControlFactory { 72 | /** 73 | * Constructor. 74 | * 75 | * @param model The model object. 76 | * @param validators properties validators map 77 | */ 78 | constructor(model, validators) { 79 | this.map = new Map(); 80 | for (const property in model) { 81 | if (!model.hasOwnProperty(property)) { 82 | continue; 83 | } 84 | this.map.set(property, { 85 | value: model[property], 86 | validators: validators[property] ? validators[property] : [], 87 | }); 88 | } 89 | } 90 | /** 91 | * Creates a new `TypedFormControl` instance. 92 | * 93 | * @param property the property of the model for which the `TypedFormControl` should be created. 94 | * @param controlOpts add custom validators to the default ones given in the constructor, optional async validators 95 | * and update mode. 96 | */ 97 | createFormControl(property, controlOpts) { 98 | const model = this.map.get(property); 99 | if (model) { 100 | return new TypedFormControl(model.value, { 101 | validators: [...model.validators, ...(controlOpts ? controlOpts.validators : [])], 102 | asyncValidators: controlOpts ? controlOpts.asyncValidators : undefined, 103 | updateOn: controlOpts ? controlOpts.updateOn : undefined, 104 | }); 105 | } 106 | return new TypedFormControl(); 107 | } 108 | } 109 | 110 | /** 111 | * Typed FormGroup. 112 | */ 113 | class TypedFormGroup extends forms.FormGroup { 114 | /** @inheritdoc */ 115 | constructor(controls, validatorOrOpts, asyncValidator) { 116 | super(controls, validatorOrOpts, asyncValidator); 117 | const map = {}; 118 | Object.keys(controls).forEach(controlName => { 119 | const control = controls[controlName]; 120 | if (control instanceof TypedFormControl) { 121 | Object.defineProperty(map, controlName, { 122 | get: () => { 123 | return control.registeredValidators; 124 | }, 125 | }); 126 | } 127 | }); 128 | this.registeredValidatorsMap = map; 129 | } 130 | /** @inheritdoc */ 131 | patchValue(value, options) { 132 | super.patchValue(value, options); 133 | } 134 | /** @inheritdoc */ 135 | get(path) { 136 | return super.get(path); 137 | } 138 | /** 139 | * Returns group if available. 140 | * 141 | * @param path path to group 142 | */ 143 | getNestedGroup(path) { 144 | const control = this.get(path); 145 | if (control instanceof TypedFormGroup) { 146 | return control; 147 | } 148 | return null; 149 | } 150 | /** 151 | * Detects if an error is present for given control name. 152 | * 153 | * @param name control name of the form group 154 | */ 155 | hasControlErrors(name) { 156 | const control = this.get(name); 157 | return !!(control && control.errors); 158 | } 159 | /** 160 | * Detects if control has validator for given control name and validator name. 161 | * 162 | * @param name control name of the form group 163 | * @param validatorName validator name 164 | */ 165 | isValidatorRegistered(name, validatorName) { 166 | return (this.registeredValidatorsMap[name] && 167 | this.registeredValidatorsMap[name].some(errorKey => errorKey === validatorName)); 168 | } 169 | /** 170 | * Returns an error key for the next error. 171 | * 172 | * @param name control key of the form group 173 | */ 174 | nextControlErrorKey(name) { 175 | const control = this.get(name); 176 | if (control && control.errors) { 177 | // try client side keys first for correct order 178 | let error = this.registeredValidatorsMap[name] && 179 | this.registeredValidatorsMap[name].find(validatorKey => control.hasError(validatorKey)); 180 | if (!error) { 181 | // fallback to all errors including custom errors set after backend calls 182 | error = Object.keys(control.errors).shift(); 183 | } 184 | if (error) { 185 | return error; 186 | } 187 | } 188 | return ''; 189 | } 190 | /** 191 | * Dispatches errors to this control and to child controls using given error map. 192 | * 193 | * @param errors error map 194 | * @param contextPath optional context path to errors set to 195 | */ 196 | dispatchErrors(errors, contextPath) { 197 | const paths = Object.keys(errors); 198 | paths.forEach(path => { 199 | const control = this.get((contextPath ? `${contextPath}.${path}` : path)); 200 | if (control) { 201 | // enables showing errors in view 202 | control.enable(); 203 | control.markAsTouched(); 204 | control.setErrors(errors[path]); 205 | } 206 | }); 207 | } 208 | } 209 | 210 | exports.BaseFormControlFactory = BaseFormControlFactory; 211 | exports.TypedFormControl = TypedFormControl; 212 | exports.TypedFormGroup = TypedFormGroup; 213 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { 2 | defaults 3 | } = require('jest-config'); 4 | 5 | module.exports = { 6 | transform: { 7 | '^.+\\.ts$': 'ts-jest' 8 | }, 9 | testRegex: "^.+\\.spec\\.ts$", 10 | moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts'], 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openapi-typescript-angular-generator", 3 | "version": "13.0.0", 4 | "description": "Code generator to generate angular-specific typescript (model/service/validator) from an openapi-specification.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/T-Systems-MMS/openapi-typescript-angular-generator.git" 8 | }, 9 | "scripts": { 10 | "build": "rollup -c && rollup -c rollup-es2015.config.js", 11 | "test": "jest --config jest.config.js", 12 | "prepare": "npm run build", 13 | "prepublishOnly": "npm test" 14 | }, 15 | "files": [ 16 | "dist", 17 | "src/mustache", 18 | "bin" 19 | ], 20 | "es2015": "dist/index.es2015.js", 21 | "main": "dist/index.js", 22 | "module": "dist/index.esm.js", 23 | "types": "dist/index.d.ts", 24 | "bin": "bin/ng-ts-codegen.js", 25 | "dependencies": { 26 | "fs-extra": "^7.0.1", 27 | "yargs": "^12.0.5" 28 | }, 29 | "keywords": [ 30 | "openapi", 31 | "typescript", 32 | "angular" 33 | ], 34 | "contributors": [ 35 | "André Jähnig", 36 | "Stefan Schubert", 37 | "Christof Hahn" 38 | ], 39 | "license": "Apache-2.0", 40 | "peerDependencies": { 41 | "@angular/forms": ">=13.0.0" 42 | }, 43 | "devDependencies": { 44 | "@angular/common": "^13.0.0", 45 | "@angular/core": "^13.0.0", 46 | "@angular/forms": "^13.0.0", 47 | "@angular/platform-browser": "^13.0.0", 48 | "@types/jest": "^29.5.0", 49 | "jest": "^29.5.0", 50 | "rollup": "^1.17.0", 51 | "rollup-plugin-typescript2": "^0.24.0", 52 | "rxjs": "^6.5.3", 53 | "ts-jest": "^29.1.0", 54 | "typescript": "4.6.4", 55 | "zone.js": "~0.10.3" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rollup-es2015.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2' 2 | import pkg from './package.json' 3 | export default { 4 | input: 'src/index.ts', 5 | output: [{ 6 | file: pkg.es2015, 7 | format: 'es', 8 | }], 9 | external: [ 10 | ...Object.keys(pkg.dependencies || {}), 11 | ...Object.keys(pkg.peerDependencies || {}), 12 | ], 13 | plugins: [ 14 | typescript({ 15 | typescript: require('typescript'), 16 | tsconfig: "tsconfig-es2015.json", 17 | }), 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2' 2 | import pkg from './package.json' 3 | export default { 4 | input: 'src/index.ts', 5 | output: [{ 6 | file: pkg.main, 7 | format: 'cjs', 8 | }, 9 | { 10 | file: pkg.module, 11 | format: 'es', 12 | }, 13 | ], 14 | external: [ 15 | ...Object.keys(pkg.dependencies || {}), 16 | ...Object.keys(pkg.peerDependencies || {}), 17 | ], 18 | plugins: [ 19 | typescript({ 20 | typescript: require('typescript'), 21 | }), 22 | ], 23 | } 24 | -------------------------------------------------------------------------------- /src/baseformcontrol.factory.spec.ts: -------------------------------------------------------------------------------- 1 | import { BaseFormControlFactory } from './baseformcontrol.factory'; 2 | import { Validators } from '@angular/forms'; 3 | 4 | interface TestType { 5 | value: string; 6 | } 7 | 8 | test('Test FormControl creation', () => { 9 | const factory = new BaseFormControlFactory( 10 | { value: 'testValue' }, 11 | { value: [['req', Validators.required]] } 12 | ); 13 | 14 | expect(Array.from(factory['map'].entries())).toEqual([ 15 | ['value', { validators: [['req', Validators.required]], value: 'testValue' }], 16 | ]); 17 | 18 | const control = factory.createFormControl('value'); 19 | expect(control.registeredValidators).toEqual(['req']); 20 | }); 21 | -------------------------------------------------------------------------------- /src/baseformcontrol.factory.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH 3 | * Riesaer Str. 5, 01129 Dresden 4 | * All rights reserved. 5 | */ 6 | import { ValidatorFn } from '@angular/forms'; 7 | import { TypedFormControl, TypedControlOptions } from './formcontrol'; 8 | 9 | /** 10 | * This is the base from control factory for each model. Based on this class, each model is implementing it's own 11 | * FormControlFactory. 12 | * Main purpose of this factory is to provide an easy way of creating form-controls with the correct validators. 13 | * The factory also ensures that model and validator match one another. 14 | */ 15 | export class BaseFormControlFactory { 16 | private map: Map; 17 | 18 | /** 19 | * Constructor. 20 | * 21 | * @param model The model object. 22 | * @param validators properties validators map 23 | */ 24 | constructor(model: T, validators: { [K in keyof T]: [string, ValidatorFn][] }) { 25 | this.map = new Map(); 26 | 27 | for (const property in model) { 28 | if (!model.hasOwnProperty(property)) { 29 | continue; 30 | } 31 | 32 | this.map.set(property, { 33 | value: model[property], 34 | validators: validators[property] ? validators[property] : [], 35 | }); 36 | } 37 | } 38 | 39 | /** 40 | * Creates a new `TypedFormControl` instance. 41 | * 42 | * @param property the property of the model for which the `TypedFormControl` should be created. 43 | * @param controlOpts add custom validators to the default ones given in the constructor, optional async validators 44 | * and update mode. 45 | */ 46 | createFormControl(property: keyof T, controlOpts?: TypedControlOptions): TypedFormControl { 47 | const model = this.map.get(property); 48 | if (model) { 49 | return new TypedFormControl(model.value, { 50 | validators: [...model.validators, ...(controlOpts ? controlOpts.validators : [])], 51 | asyncValidators: controlOpts ? controlOpts.asyncValidators : undefined, 52 | updateOn: controlOpts ? controlOpts.updateOn : undefined, 53 | }); 54 | } 55 | return new TypedFormControl(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/formcontrol.spec.ts: -------------------------------------------------------------------------------- 1 | import { TypedFormControl } from './formcontrol'; 2 | import { Validators, ValidationErrors, AbstractControl } from '@angular/forms'; 3 | 4 | test('Create TypedFormControl', () => { 5 | const formControl = new TypedFormControl('test', { 6 | validators: [['req', Validators.required]], 7 | asyncValidators: [ 8 | [ 9 | 'asyncReq', 10 | (control: AbstractControl) => { 11 | return new Promise(resolve => { 12 | resolve({ asyncReq: 'Is required!' }); 13 | }); 14 | }, 15 | ], 16 | ], 17 | }); 18 | 19 | expect(formControl.registeredValidators).toEqual(['req', 'asyncReq']); 20 | }); 21 | 22 | test('Create TypedFormControl with only validators', () => { 23 | const formControl = new TypedFormControl('test', { 24 | validators: [['req', Validators.required]], 25 | }); 26 | 27 | expect(formControl.registeredValidators).toEqual(['req']); 28 | }); 29 | -------------------------------------------------------------------------------- /src/formcontrol.ts: -------------------------------------------------------------------------------- 1 | import { FormControl, ValidatorFn, AsyncValidatorFn, FormControlStatus } from '@angular/forms'; 2 | import { Observable } from 'rxjs'; 3 | 4 | /** 5 | * Validator options to have mapping key -> validator function. 6 | */ 7 | export interface ValidatorOptions { 8 | /** mapping error key -> validator function */ 9 | validators: [string, ValidatorFn][]; 10 | /** mapping error key -> validator function */ 11 | asyncValidators?: [string, AsyncValidatorFn][]; 12 | } 13 | 14 | /** 15 | * Simplified options interface. 16 | */ 17 | export interface TypedControlOptions extends ValidatorOptions { 18 | /** updateOn */ 19 | updateOn?: 'change' | 'blur' | 'submit'; 20 | } 21 | 22 | /** @inheritdoc */ 23 | export class TypedFormControl extends FormControl { 24 | /** @inheritdoc */ 25 | readonly value: T; 26 | /** @inheritdoc */ 27 | readonly valueChanges: Observable; 28 | /** @inheritdoc */ 29 | readonly statusChanges: Observable; 30 | /** holds all possible validator names extracted by the given validators */ 31 | readonly registeredValidators: string[]; 32 | 33 | /** @inheritdoc */ 34 | constructor(formState?: T, opts?: TypedControlOptions) { 35 | super(formState, { 36 | validators: opts ? opts.validators.map(validator => validator && validator[1]) : null, 37 | asyncValidators: 38 | opts && opts.asyncValidators ? opts.asyncValidators.map(validator => validator && validator[1]) : null, 39 | updateOn: opts && opts.updateOn ? opts.updateOn : 'change', 40 | }); 41 | 42 | this.registeredValidators = this.generateValidatorNames(opts); 43 | } 44 | 45 | /** @inheritdoc */ 46 | patchValue( 47 | value: T, 48 | options?: { 49 | onlySelf?: boolean; 50 | emitEvent?: boolean; 51 | emitModelToViewChange?: boolean; 52 | emitViewToModelChange?: boolean; 53 | } 54 | ) { 55 | super.patchValue(value, options); 56 | } 57 | 58 | /** @inheritdoc */ 59 | setValue( 60 | value: T, 61 | options?: { 62 | onlySelf?: boolean; 63 | emitEvent?: boolean; 64 | emitModelToViewChange?: boolean; 65 | emitViewToModelChange?: boolean; 66 | } 67 | ) { 68 | super.setValue(value, options); 69 | } 70 | 71 | /** @inheritdoc */ 72 | reset( 73 | formState?: T, 74 | options?: { 75 | onlySelf?: boolean; 76 | emitEvent?: boolean; 77 | } 78 | ) { 79 | super.reset(formState, options); 80 | } 81 | 82 | /** 83 | * Sets new validators and updates possible error keys list. 84 | * 85 | * @param newValidators new validators 86 | */ 87 | setNewValidators(newValidators: ValidatorOptions) { 88 | super.setValidators(newValidators ? newValidators.validators.map(validator => validator && validator[1]) : null); 89 | super.setAsyncValidators( 90 | newValidators && newValidators.asyncValidators 91 | ? newValidators.asyncValidators.map(validator => validator && validator[1]) 92 | : null 93 | ); 94 | (this as { registeredValidators: any }).registeredValidators = this.generateValidatorNames(newValidators); 95 | } 96 | 97 | /** 98 | * Generates validator name list. 99 | * 100 | * @param validatorOpts options to handle 101 | */ 102 | private generateValidatorNames(validatorOpts: ValidatorOptions | undefined) { 103 | let keys: string[] = []; 104 | if (validatorOpts) { 105 | let validatorsList = [validatorOpts.validators]; 106 | if (validatorOpts.asyncValidators) { 107 | validatorsList.push(validatorOpts.asyncValidators); 108 | } 109 | validatorsList.forEach((validators: [string, ValidatorFn | AsyncValidatorFn][]) => { 110 | keys.push( 111 | ...validators 112 | .map(validator => validator && validator[0]) 113 | // filter duplicates 114 | .filter((key, index, array) => array.indexOf(key) === index) 115 | ); 116 | }); 117 | } 118 | return keys; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/formgroup.spec.ts: -------------------------------------------------------------------------------- 1 | import { BaseFormControlFactory } from './baseformcontrol.factory'; 2 | import { TypedFormGroup } from './formgroup'; 3 | import { Validators } from '@angular/forms'; 4 | 5 | interface TestType { 6 | value: string; 7 | nested: NestedType; 8 | } 9 | 10 | interface NestedType { 11 | deepValue: string; 12 | } 13 | 14 | test('Test FormGroupControl creation #1', () => { 15 | const factory = new BaseFormControlFactory( 16 | { value: 'testValue', nested: { deepValue: 'deepTestValue' } }, 17 | { value: [['req', Validators.required]], nested: [] } 18 | ); 19 | 20 | const nestedFactory = new BaseFormControlFactory( 21 | { deepValue: 'deepTestValue' }, 22 | { deepValue: [['req', Validators.required]] } 23 | ); 24 | 25 | expect(Array.from(factory['map'].entries())).toEqual([ 26 | ['value', { validators: [['req', Validators.required]], value: 'testValue' }], 27 | ['nested', { validators: [], value: { deepValue: 'deepTestValue' } }], 28 | ]); 29 | expect(Array.from(nestedFactory['map'].entries())).toEqual([ 30 | ['deepValue', { validators: [['req', Validators.required]], value: 'deepTestValue' }], 31 | ]); 32 | 33 | const control = factory.createFormControl('value'); 34 | expect(control.registeredValidators).toEqual(['req']); 35 | 36 | const valueControl = factory.createFormControl('value'); 37 | const nestedControl = nestedFactory.createFormControl('deepValue'); 38 | const nestedGroup = new TypedFormGroup({ 39 | deepValue: nestedControl, 40 | }); 41 | const group = new TypedFormGroup({ 42 | nested: nestedGroup, 43 | value: valueControl, 44 | }); 45 | 46 | expect(nestedGroup.registeredValidatorsMap.deepValue).toEqual(['req']); 47 | expect(group.registeredValidatorsMap.value).toEqual(['req']); 48 | 49 | valueControl.setErrors({ req: 'Is Required!', other: 'Other Error!' }); 50 | nestedControl.setErrors({ req: 'Is Required!' }); 51 | 52 | expect(group.parent).toBeUndefined(); 53 | expect(valueControl.parent).toEqual(group); 54 | expect(nestedControl.parent).toEqual(nestedGroup); 55 | 56 | expect(group.hasControlErrors('value')).toBe(true); 57 | expect(group.controls['nested'].status).toBe('INVALID'); 58 | const returnedGroup = group.getNestedGroup('nested'); 59 | expect(returnedGroup ? returnedGroup.hasControlErrors('deepValue') : false).toBe(true); 60 | expect(nestedGroup.hasControlErrors('deepValue')).toBe(true); 61 | expect(group.isValidatorRegistered('value', 'required')).toBe(false); 62 | expect(nestedGroup.isValidatorRegistered('deepValue', 'required')).toBe(false); 63 | expect(group.nextControlErrorKey('value')).toBe('req'); 64 | expect(nestedGroup.nextControlErrorKey('deepValue')).toBe('req'); 65 | 66 | valueControl.setErrors({ other: 'Other Error!' }); 67 | expect(group.nextControlErrorKey('value')).toBe('other'); 68 | }); 69 | 70 | test('Test FormGroupControl creation #2', () => { 71 | const nestedFactory = new BaseFormControlFactory( 72 | { deepValue: 'deepTestValue' }, 73 | { deepValue: [['req', Validators.required]] } 74 | ); 75 | 76 | const nestedControl = nestedFactory.createFormControl('deepValue'); 77 | const nestedGroup = new TypedFormGroup({ 78 | deepValue: nestedControl, 79 | }); 80 | 81 | expect(nestedGroup.registeredValidatorsMap.deepValue).toEqual(['req']); 82 | 83 | nestedControl.setNewValidators({ validators: [['req', Validators.required], ['max', Validators.max(2)]] }); 84 | 85 | expect(nestedGroup.registeredValidatorsMap.deepValue).toEqual(['req', 'max']); 86 | }); 87 | -------------------------------------------------------------------------------- /src/formgroup.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AbstractControl, 3 | AbstractControlOptions, 4 | AsyncValidatorFn, 5 | FormGroup, 6 | ValidatorFn, 7 | ValidationErrors, 8 | } from '@angular/forms'; 9 | import { Observable } from 'rxjs'; 10 | import { TypedFormControl } from './formcontrol'; 11 | 12 | /** 13 | * Typed FormGroup. 14 | */ 15 | export class TypedFormGroup extends FormGroup { 16 | /** @inheritdoc */ 17 | readonly value: T; 18 | /** @inheritdoc */ 19 | readonly valueChanges: Observable; 20 | /** holds a map with control names to possible validator names */ 21 | readonly registeredValidatorsMap: { [controlName in keyof T]: string[] }; 22 | 23 | /** @inheritdoc */ 24 | constructor( 25 | controls: { [key in keyof T]: AbstractControl }, 26 | validatorOrOpts?: ValidatorFn | Array | AbstractControlOptions | null, 27 | asyncValidator?: AsyncValidatorFn | Array | null 28 | ) { 29 | super(controls, validatorOrOpts, asyncValidator); 30 | const map = {}; 31 | Object.keys(controls).forEach(controlName => { 32 | const control = controls[controlName]; 33 | if (control instanceof TypedFormControl) { 34 | Object.defineProperty(map, controlName, { 35 | get: () => { 36 | return control.registeredValidators; 37 | }, 38 | }); 39 | } 40 | }); 41 | this.registeredValidatorsMap = map as { [key in keyof T]: string[] }; 42 | } 43 | 44 | /** @inheritdoc */ 45 | patchValue(value: Partial | T, options?: { onlySelf?: boolean; emitEvent?: boolean }): void { 46 | super.patchValue(value, options); 47 | } 48 | 49 | /** @inheritdoc */ 50 | get(path: Array> | Extract): AbstractControl | null { 51 | return super.get(path); 52 | } 53 | 54 | /** 55 | * Returns group if available. 56 | * 57 | * @param path path to group 58 | */ 59 | getNestedGroup(path: Extract): TypedFormGroup | null { 60 | const control = this.get(path); 61 | if (control instanceof TypedFormGroup) { 62 | return control; 63 | } 64 | return null; 65 | } 66 | 67 | /** 68 | * Detects if an error is present for given control name. 69 | * 70 | * @param name control name of the form group 71 | */ 72 | hasControlErrors(name: Extract): boolean { 73 | const control = this.get(name); 74 | return !!(control && control.errors); 75 | } 76 | 77 | /** 78 | * Detects if control has validator for given control name and validator name. 79 | * 80 | * @param name control name of the form group 81 | * @param validatorName validator name 82 | */ 83 | isValidatorRegistered(name: Extract, validatorName: string): boolean { 84 | return ( 85 | this.registeredValidatorsMap[name] && 86 | this.registeredValidatorsMap[name].some(errorKey => errorKey === validatorName) 87 | ); 88 | } 89 | 90 | /** 91 | * Returns an error key for the next error. 92 | * 93 | * @param name control key of the form group 94 | */ 95 | nextControlErrorKey(name: Extract): string { 96 | const control = this.get(name); 97 | if (control && control.errors) { 98 | // try client side keys first for correct order 99 | let error = 100 | this.registeredValidatorsMap[name] && 101 | this.registeredValidatorsMap[name].find(validatorKey => control.hasError(validatorKey)); 102 | if (!error) { 103 | // fallback to all errors including custom errors set after backend calls 104 | error = Object.keys(control.errors).shift(); 105 | } 106 | if (error) { 107 | return error; 108 | } 109 | } 110 | return ''; 111 | } 112 | 113 | /** 114 | * Dispatches errors to this control and to child controls using given error map. 115 | * 116 | * @param errors error map 117 | * @param contextPath optional context path to errors set to 118 | */ 119 | dispatchErrors(errors: { [key: string]: ValidationErrors }, contextPath?: string) { 120 | const paths = Object.keys(errors); 121 | paths.forEach(path => { 122 | const control = this.get(>(contextPath ? `${contextPath}.${path}` : path)); 123 | if (control) { 124 | // enables showing errors in view 125 | control.enable(); 126 | control.markAsTouched(); 127 | control.setErrors(errors[path]); 128 | } 129 | }); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './baseformcontrol.factory'; 2 | export * from './formgroup'; 3 | export * from './formcontrol'; 4 | -------------------------------------------------------------------------------- /src/mustache/api.module.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | import { NgModule, ModuleWithProviders, SkipSelf, Optional } from '@angular/core'; 5 | import { Configuration } from './configuration'; 6 | {{#useHttpClient}}import { HttpClient } from '@angular/common/http';{{/useHttpClient}} 7 | {{^useHttpClient}}import { Http } from '@angular/http';{{/useHttpClient}} 8 | 9 | {{#apiInfo}} 10 | {{#apis}} 11 | import { {{classname}} } from './{{importPath}}'; 12 | {{/apis}} 13 | {{/apiInfo}} 14 | 15 | @NgModule({ 16 | imports: [], 17 | declarations: [], 18 | exports: [], 19 | providers: [ 20 | {{#apiInfo}}{{#apis}}{{classname}}{{#hasMore}}, 21 | {{/hasMore}}{{/apis}}{{/apiInfo}} ] 22 | }) 23 | export class ApiModule { 24 | public static forRoot(configurationFactory: (...args: any[]) => Configuration, deps?: any[]): ModuleWithProviders { 25 | return { 26 | ngModule: ApiModule, 27 | providers: [ { provide: Configuration, useFactory: configurationFactory, deps } ] 28 | }; 29 | } 30 | 31 | constructor( @Optional() @SkipSelf() parentModule: ApiModule, 32 | @Optional() http: {{#useHttpClient}}HttpClient{{/useHttpClient}}{{^useHttpClient}}Http{{/useHttpClient}}) { 33 | if (parentModule) { 34 | throw new Error('ApiModule is already loaded. Import in your base AppModule only.'); 35 | } 36 | if (!http) { 37 | throw new Error('You need to import the {{#useHttpClient}}HttpClientModule{{/useHttpClient}}{{^useHttpClient}}HttpModule{{/useHttpClient}} in your AppModule! \n' + 38 | 'See also https://github.com/angular/angular/issues/20575'); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/mustache/api.service.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | {{>licenseInfo}} 5 | /* tslint:disable:no-unused-variable member-ordering */ 6 | 7 | import { Inject, Injectable, Optional } from '@angular/core'; 8 | {{>apiParamValidatorImports}} 9 | {{#useHttpClient}} 10 | import { HttpClient, HttpHeaders, HttpParams, 11 | HttpResponse, HttpEvent } from '@angular/common/http'; 12 | import { CustomHttpUrlEncodingCodec } from '../encoder'; 13 | {{/useHttpClient}} 14 | {{^useHttpClient}} 15 | import { Http, Headers, URLSearchParams } from '@angular/http'; 16 | import { RequestMethod, RequestOptions, RequestOptionsArgs } from '@angular/http'; 17 | import { Response, ResponseContentType } from '@angular/http'; 18 | import { CustomQueryEncoderHelper } from '../encoder'; 19 | {{/useHttpClient}} 20 | 21 | {{^useRxJS6}} 22 | import { Observable } from 'rxjs/Observable'; 23 | import { Subject } from 'rxjs/Subject'; 24 | import { Subscription } from 'rxjs/Subscription'; 25 | {{/useRxJS6}} 26 | {{#useRxJS6}} 27 | import { Observable, Subject, Subscription } from 'rxjs'; 28 | import { catchError, takeUntil, tap, share } from 'rxjs/operators'; 29 | {{/useRxJS6}} 30 | {{^useHttpClient}} 31 | import '../rxjs-operators'; 32 | import 'rxjs/add/operator/takeUntil'; 33 | import 'rxjs/add/operator/tap'; 34 | import 'rxjs/add/operator/share'; 35 | {{/useHttpClient}} 36 | 37 | {{#imports}} 38 | import { {{classname}} } from '../{{filename}}'; 39 | {{/imports}} 40 | 41 | import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; 42 | import { Configuration } from '../configuration'; 43 | {{#withInterfaces}} 44 | import { {{classname}}Interface } from './{{classFilename}}Interface'; 45 | {{/withInterfaces}} 46 | 47 | {{#operations}} 48 | {{>apiPartialMap}} 49 | 50 | {{#description}} 51 | /** 52 | * {{&description}} 53 | */ 54 | {{/description}} 55 | {{^providedInRoot}} 56 | @Injectable() 57 | {{/providedInRoot}} 58 | {{#providedInRoot}} 59 | @Injectable({ 60 | providedIn: 'root' 61 | }) 62 | {{/providedInRoot}} 63 | {{#withInterfaces}} 64 | export class {{classname}} implements {{classname}}Interface { 65 | {{/withInterfaces}} 66 | {{^withInterfaces}} 67 | export class {{classname}} { 68 | {{/withInterfaces}} 69 | 70 | protected basePath = '{{{basePath}}}'; 71 | public defaultHeaders = new {{#useHttpClient}}Http{{/useHttpClient}}Headers(); 72 | public configuration = new Configuration(); 73 | private cancelMap: {[key: string]: Subject} = {}; 74 | 75 | constructor(protected {{#useHttpClient}}httpClient: HttpClient{{/useHttpClient}}{{^useHttpClient}}http: Http{{/useHttpClient}}, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) { 76 | 77 | if (configuration) { 78 | this.configuration = configuration; 79 | this.configuration.basePath = configuration.basePath || basePath || this.basePath; 80 | 81 | } else { 82 | this.configuration.basePath = basePath || this.basePath; 83 | } 84 | } 85 | 86 | /** 87 | * @param consumes string[] mime-types 88 | * @return true: consumes contains 'multipart/form-data', false: otherwise 89 | */ 90 | private canConsumeForm(consumes: string[]): boolean { 91 | const form = 'multipart/form-data'; 92 | for (const consume of consumes) { 93 | if (form === consume) { 94 | return true; 95 | } 96 | } 97 | return false; 98 | } 99 | 100 | {{^useHttpClient}} 101 | {{! Before HttpClient implementation or method overloading we relied on 2 functions, 1 to return the straight body as json 102 | and another to get the full response.}} 103 | {{#operation}} 104 | /** 105 | * {{¬es}} 106 | {{#summary}} 107 | * @summary {{&summary}} 108 | {{/summary}} 109 | {{#allParams}}* @param {{paramName}} {{description}} 110 | {{/allParams}}*/ 111 | {{! if you change this method signature, also change the version below }} 112 | public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{^useHttpClient}}{{#hasParams}}, {{/hasParams}}extraHttpRequestParams?: RequestOptionsArgs{{/useHttpClient}}): Observable<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}{}{{/returnType}}> { 113 | return this.{{nickname}}WithHttpInfo({{#allParams}}{{paramName}}, {{/allParams}}extraHttpRequestParams) 114 | .map((response: Response) => { 115 | if (response.status === 204) { 116 | return undefined; 117 | } else { 118 | {{^isResponseFile}} 119 | return response.json() || {}; 120 | {{/isResponseFile}} 121 | {{#isResponseFile}} 122 | return response.blob(); 123 | {{/isResponseFile}} 124 | } 125 | }); 126 | } 127 | 128 | {{/operation}} 129 | {{/useHttpClient}} 130 | 131 | {{#operation}} 132 | {{>apiCallByPartialMap}} 133 | 134 | /** 135 | * {{summary}} 136 | * {{notes}} 137 | {{#allParams}}* @param {{paramName}} {{description}} 138 | {{/allParams}}{{#useHttpClient}}* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. 139 | * @param reportProgress flag to report request and response progress.{{/useHttpClient}} 140 | * @param cancelPreviousRequest set whether or not the previous request for the same operation should be cancelled if it is still running. 141 | */ 142 | {{#useHttpClient}} 143 | public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}observe?: 'body', reportProgress?: boolean, cancelPreviousRequest?: boolean): Observable<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>; 144 | public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}observe?: 'response', reportProgress?: boolean, cancelPreviousRequest?: boolean): Observable>; 145 | public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}observe?: 'events', reportProgress?: boolean, cancelPreviousRequest?: boolean): Observable>; 146 | public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}observe: any = 'body', reportProgress: boolean = false, cancelPreviousRequest: boolean = true): Observable { 147 | {{/useHttpClient}} 148 | {{^useHttpClient}} 149 | public {{nickname}}WithHttpInfo({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}extraHttpRequestParams?: RequestOptionsArgs, cancelPreviousRequest: boolean = true): Observable { 150 | {{/useHttpClient}} 151 | {{#allParams}} 152 | {{#required}} 153 | if ({{paramName}} === null || {{paramName}} === undefined) { 154 | throw new Error('Required parameter {{paramName}} was null or undefined when calling {{nickname}}.'); 155 | } 156 | {{/required}} 157 | {{/allParams}} 158 | 159 | {{#hasQueryParams}} 160 | {{#useHttpClient}} 161 | let queryParameters = new HttpParams({encoder: new CustomHttpUrlEncodingCodec()}); 162 | {{/useHttpClient}} 163 | {{^useHttpClient}} 164 | let queryParameters = new URLSearchParams('', new CustomQueryEncoderHelper()); 165 | {{/useHttpClient}} 166 | {{#queryParams}} 167 | {{#isListContainer}} 168 | if ({{paramName}}) { 169 | {{#isCollectionFormatMulti}} 170 | {{paramName}}.forEach((element) => { 171 | {{#useHttpClient}}queryParameters = {{/useHttpClient}}queryParameters.append('{{baseName}}', element); 172 | }) 173 | {{/isCollectionFormatMulti}} 174 | {{^isCollectionFormatMulti}} 175 | {{#useHttpClient}}queryParameters = {{/useHttpClient}}queryParameters.set('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}'])); 176 | {{/isCollectionFormatMulti}} 177 | } 178 | {{/isListContainer}} 179 | {{^isListContainer}} 180 | if ({{paramName}} !== undefined && {{paramName}} !== null) { 181 | {{#isDateTime}} 182 | {{#useHttpClient}}queryParameters = {{/useHttpClient}}queryParameters.set('{{baseName}}', {{paramName}}.toISOString()); 183 | {{/isDateTime}} 184 | {{^isDateTime}} 185 | {{#useHttpClient}}queryParameters = {{/useHttpClient}}queryParameters.set('{{baseName}}', {{paramName}}); 186 | {{/isDateTime}} 187 | } 188 | {{/isListContainer}} 189 | {{/queryParams}} 190 | 191 | {{/hasQueryParams}} 192 | let headers = {{#useHttpClient}}this.defaultHeaders;{{/useHttpClient}}{{^useHttpClient}}new Headers(this.defaultHeaders.toJSON()); // https://github.com/angular/angular/issues/6845{{/useHttpClient}} 193 | {{#headerParams}} 194 | if(typeof this.configuration.defaultHeaderValue === 'function' && ({{paramName}} === undefined || {{paramName}} === null)) { 195 | {{paramName}} = this.configuration.defaultHeaderValue('{{paramName}}', '{{operationIdOriginal}}'); 196 | } 197 | {{#isListContainer}} 198 | if ({{paramName}}) { 199 | {{#useHttpClient}}headers = {{/useHttpClient}}headers.set('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}'])); 200 | } 201 | {{/isListContainer}} 202 | {{^isListContainer}} 203 | if ({{paramName}} !== undefined && {{paramName}} !== null) { 204 | {{#useHttpClient}}headers = {{/useHttpClient}}headers.set('{{baseName}}', String({{paramName}})); 205 | } 206 | {{/isListContainer}} 207 | {{/headerParams}} 208 | 209 | {{#authMethods}} 210 | // authentication ({{name}}) required 211 | {{#isApiKey}} 212 | {{#isKeyInHeader}} 213 | if (this.configuration.apiKeys && this.configuration.apiKeys["{{keyParamName}}"]) { 214 | {{#useHttpClient}}headers = {{/useHttpClient}}headers.set('{{keyParamName}}', this.configuration.apiKeys["{{keyParamName}}"]); 215 | } 216 | 217 | {{/isKeyInHeader}} 218 | {{#isKeyInQuery}} 219 | if (this.configuration.apiKeys && this.configuration.apiKeys["{{keyParamName}}"]) { 220 | {{#useHttpClient}}queryParameters = {{/useHttpClient}}queryParameters.set('{{keyParamName}}', this.configuration.apiKeys["{{keyParamName}}"]); 221 | } 222 | 223 | {{/isKeyInQuery}} 224 | {{/isApiKey}} 225 | {{#isBasic}} 226 | if (this.configuration.username || this.configuration.password) { 227 | {{#useHttpClient}}headers = {{/useHttpClient}}headers.set('Authorization', 'Basic ' + btoa(this.configuration.username + ':' + this.configuration.password)); 228 | } 229 | 230 | {{/isBasic}} 231 | {{#isOAuth}} 232 | if (this.configuration.accessToken) { 233 | const accessToken = typeof this.configuration.accessToken === 'function' 234 | ? this.configuration.accessToken() 235 | : this.configuration.accessToken; 236 | {{#useHttpClient}}headers = {{/useHttpClient}}headers.set('Authorization', 'Bearer ' + accessToken); 237 | } 238 | 239 | {{/isOAuth}} 240 | {{/authMethods}} 241 | // to determine the Accept header 242 | let httpHeaderAccepts: string[] = [ 243 | {{#produces}} 244 | '{{{mediaType}}}'{{#hasMore}},{{/hasMore}} 245 | {{/produces}} 246 | ]; 247 | const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts); 248 | if (httpHeaderAcceptSelected !== undefined) { 249 | {{^useHttpClient}} 250 | headers.set('Accept', httpHeaderAcceptSelected); 251 | {{/useHttpClient}} 252 | {{#useHttpClient}} 253 | headers = headers.set('Accept', httpHeaderAcceptSelected); 254 | {{/useHttpClient}} 255 | } 256 | 257 | // to determine the Content-Type header 258 | const consumes: string[] = [ 259 | {{#consumes}} 260 | '{{{mediaType}}}'{{#hasMore}},{{/hasMore}} 261 | {{/consumes}} 262 | ]; 263 | {{#bodyParam}} 264 | const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); 265 | if (httpContentTypeSelected !== undefined) { 266 | {{^useHttpClient}} 267 | headers.set('Content-Type', httpContentTypeSelected); 268 | {{/useHttpClient}} 269 | {{#useHttpClient}} 270 | headers = headers.set('Content-Type', httpContentTypeSelected); 271 | {{/useHttpClient}} 272 | } 273 | {{/bodyParam}} 274 | 275 | {{#hasFormParams}} 276 | const canConsumeForm = this.canConsumeForm(consumes); 277 | 278 | let formParams: { append(param: string, value: any): any; }; 279 | let useForm = false; 280 | let convertFormParamsToString = false; 281 | {{#formParams}} 282 | {{#isFile}} 283 | // use FormData to transmit files using content-type "multipart/form-data" 284 | // see https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data 285 | useForm = canConsumeForm; 286 | {{/isFile}} 287 | {{/formParams}} 288 | if (useForm) { 289 | formParams = new FormData(); 290 | } else { 291 | {{#useHttpClient}} 292 | formParams = new HttpParams({encoder: new CustomHttpUrlEncodingCodec()}); 293 | {{/useHttpClient}} 294 | {{^useHttpClient}} 295 | // TODO: this fails if a parameter is a file, the api can't consume "multipart/form-data" and a blob is passed. 296 | convertFormParamsToString = true; 297 | formParams = new URLSearchParams('', new CustomQueryEncoderHelper()); 298 | // set the content-type explicitly to avoid having it set to 'text/plain' 299 | headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); 300 | {{/useHttpClient}} 301 | } 302 | 303 | {{#formParams}} 304 | {{#isListContainer}} 305 | if ({{paramName}}) { 306 | {{#isCollectionFormatMulti}} 307 | {{paramName}}.forEach((element) => { 308 | {{#useHttpClient}}formParams = {{/useHttpClient}}formParams.append('{{baseName}}', element){{#useHttpClient}} || formParams{{/useHttpClient}}; 309 | }) 310 | {{/isCollectionFormatMulti}} 311 | {{^isCollectionFormatMulti}} 312 | {{#useHttpClient}}formParams = {{/useHttpClient}}formParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}'])){{#useHttpClient}} || formParams{{/useHttpClient}}; 313 | {{/isCollectionFormatMulti}} 314 | } 315 | {{/isListContainer}} 316 | {{^isListContainer}} 317 | if ({{paramName}} !== undefined) { 318 | {{#useHttpClient}}formParams = {{/useHttpClient}}formParams.append('{{baseName}}', {{paramName}}){{#useHttpClient}} || formParams{{/useHttpClient}}; 319 | } 320 | {{/isListContainer}} 321 | {{/formParams}} 322 | 323 | {{/hasFormParams}} 324 | {{#useHttpClient}} 325 | let handle = this.httpClient.{{httpMethod}}{{^isResponseFile}}<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>{{/isResponseFile}}(`${this.configuration.basePath}{{{path}}}`,{{#isBodyAllowed}} 326 | {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}{{#hasFormParams}}convertFormParamsToString ? formParams.toString() : formParams{{/hasFormParams}}{{^hasFormParams}}null{{/hasFormParams}}{{/bodyParam}},{{/isBodyAllowed}} 327 | { 328 | {{#hasQueryParams}} 329 | params: queryParameters, 330 | {{/hasQueryParams}} 331 | {{#isResponseFile}} 332 | responseType: "blob", 333 | {{/isResponseFile}} 334 | withCredentials: this.configuration.withCredentials, 335 | headers: headers, 336 | observe: observe, 337 | reportProgress: reportProgress 338 | } 339 | ); 340 | 341 | if (cancelPreviousRequest) { 342 | if (this.cancelMap['{{operationIdOriginal}}']) { 343 | this.cancelMap['{{operationIdOriginal}}'].next(); 344 | } 345 | this.cancelMap['{{operationIdOriginal}}'] = '{{httpMethod}}'.toUpperCase() === 'GET' ? new Subject() : null; 346 | if(this.cancelMap['{{operationIdOriginal}}']) { 347 | handle = handle.pipe(takeUntil(this.cancelMap['{{operationIdOriginal}}'])); 348 | } 349 | } 350 | 351 | if (typeof this.configuration.beforeHandler === 'function') { 352 | this.configuration.beforeHandler('{{operationIdOriginal}}', '{{httpMethod}}'.toUpperCase()); 353 | } 354 | let afterSubscription: Subscription; 355 | const afterHandler = (result: any = null) => { 356 | if (afterSubscription) { 357 | afterSubscription.unsubscribe(); 358 | } 359 | // stop cancellation to prevent calling afterHandler on next service call 360 | if (cancelPreviousRequest && this.cancelMap['{{operationIdOriginal}}']) { 361 | this.cancelMap['{{operationIdOriginal}}'].complete(); 362 | delete this.cancelMap['{{operationIdOriginal}}']; 363 | } 364 | if (typeof this.configuration.afterHandler === 'function') { 365 | this.configuration.afterHandler( 366 | '{{operationIdOriginal}}', 367 | '{{httpMethod}}'.toUpperCase(), 368 | result ? result : new Error('CANCELLED') 369 | ); 370 | } 371 | }; 372 | handle = handle.pipe(share()); 373 | afterSubscription = handle.subscribe( 374 | result => afterHandler(result), 375 | err => afterHandler(err), 376 | () => afterHandler() 377 | ); 378 | 379 | if (typeof this.configuration.errorHandler === 'function') { 380 | return handle.pipe(catchError(err => this.configuration.errorHandler(err, '{{operationIdOriginal}}'))); 381 | } 382 | return handle; 383 | {{/useHttpClient}} 384 | {{^useHttpClient}} 385 | let requestOptions: RequestOptionsArgs = new RequestOptions({ 386 | method: {{httpMethod}}, 387 | headers: headers, 388 | {{#bodyParam}} 389 | body: {{paramName}} == null ? '' : JSON.stringify({{paramName}}), // https://github.com/angular/angular/issues/10612 390 | {{/bodyParam}} 391 | {{#hasFormParams}} 392 | body: convertFormParamsToString ? formParams.toString() : formParams, 393 | {{/hasFormParams}} 394 | {{#isResponseFile}} 395 | responseType: ResponseContentType.Blob, 396 | {{/isResponseFile}} 397 | {{#hasQueryParams}} 398 | search: queryParameters, 399 | {{/hasQueryParams}} 400 | withCredentials:this.configuration.withCredentials 401 | }); 402 | // issues#4037 403 | if (extraHttpRequestParams) { 404 | requestOptions = (Object).assign(requestOptions, extraHttpRequestParams); 405 | } 406 | 407 | let handle = this.http.request(`${this.configuration.basePath}{{{path}}}`, requestOptions); 408 | 409 | if (cancelPreviousRequest) { 410 | if (this.cancelMap['{{operationIdOriginal}}']) { 411 | this.cancelMap['{{operationIdOriginal}}'].next(); 412 | } 413 | this.cancelMap['{{operationIdOriginal}}'] = '{{httpMethod}}'.toUpperCase() === 'GET' ? new Subject() : null; 414 | if(this.cancelMap['{{operationIdOriginal}}']) { 415 | handle = handle.takeUntil(this.cancelMap['{{operationIdOriginal}}']); 416 | } 417 | } 418 | 419 | if (typeof this.configuration.beforeHandler === 'function') { 420 | this.configuration.beforeHandler('{{operationIdOriginal}}', '{{httpMethod}}'.toUpperCase()); 421 | } 422 | let afterSubscription: Subscription; 423 | const afterHandler = (result: any = null) => { 424 | if (afterSubscription) { 425 | afterSubscription.unsubscribe(); 426 | } 427 | // stop cancellation to prevent calling afterHandler on next service call 428 | if (cancelPreviousRequest && this.cancelMap['{{operationIdOriginal}}']) { 429 | this.cancelMap['{{operationIdOriginal}}'].complete(); 430 | delete this.cancelMap['{{operationIdOriginal}}']; 431 | } 432 | if (typeof this.configuration.afterHandler === 'function') { 433 | this.configuration.afterHandler( 434 | '{{operationIdOriginal}}', 435 | '{{httpMethod}}'.toUpperCase(), 436 | result ? result : new Error('CANCELLED') 437 | ); 438 | } 439 | }; 440 | handle = handle.share(); 441 | afterSubscription = handle.subscribe( 442 | result => afterHandler(result), 443 | err => afterHandler(err), 444 | () => afterHandler() 445 | ); 446 | 447 | if (typeof this.configuration.errorHandler === 'function') { 448 | return handle.catch(err => this.configuration.errorHandler(err, '{{operationIdOriginal}}')); 449 | } 450 | return handle; 451 | {{/useHttpClient}} 452 | } 453 | 454 | {{/operation}}} 455 | 456 | export namespace {{classname}} { 457 | export enum Operations { 458 | {{#operation}} 459 | {{operationIdOriginal}} = '{{operationIdOriginal}}'{{^-last}},{{/-last}} 460 | {{/operation}} 461 | } 462 | } 463 | {{/operations}} 464 | -------------------------------------------------------------------------------- /src/mustache/apiCallByPartialMap.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | {{#useHttpClient}} 5 | 6 | /** 7 | * {{summary}} by map. 8 | * {{notes}} 9 | * @param map parameters map to set partial amount of parameters easily 10 | * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. 11 | * @param reportProgress flag to report request and response progress. 12 | * @param cancelPreviousRequest set whether or not the previous request for the same operation should be cancelled if it is still running. 13 | */ 14 | public {{nickname}}ByMap( 15 | map: {{operationIdCamelCase}}.PartialParamMap, 16 | observe?: 'body', 17 | reportProgress?: boolean, 18 | cancelPreviousRequest?: boolean): Observable<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>; 19 | public {{nickname}}ByMap( 20 | map: {{operationIdCamelCase}}.PartialParamMap, 21 | observe?: 'response', 22 | reportProgress?: boolean, 23 | cancelPreviousRequest?: boolean): Observable>; 24 | public {{nickname}}ByMap( 25 | map: {{operationIdCamelCase}}.PartialParamMap, 26 | observe?: 'events', 27 | reportProgress?: boolean, 28 | cancelPreviousRequest?: boolean): Observable>; 29 | public {{nickname}}ByMap( 30 | map: {{operationIdCamelCase}}.PartialParamMap, 31 | observe: any = 'body', 32 | reportProgress: boolean = false, 33 | cancelPreviousRequest: boolean = true): Observable { 34 | return this.{{nickname}}({{#allParams}} 35 | map.{{paramName}},{{/allParams}} 36 | observe, 37 | reportProgress, 38 | cancelPreviousRequest 39 | ); 40 | } 41 | {{/useHttpClient}} 42 | -------------------------------------------------------------------------------- /src/mustache/apiParamValidatorImports.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | import { ValidatorFn, Validators } from '@angular/forms'; 5 | -------------------------------------------------------------------------------- /src/mustache/apiPartialMap.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | {{#operation}} 5 | /** 6 | * Namespace for {{nickname}}. 7 | */ 8 | export namespace {{operationIdCamelCase}} { 9 | /** 10 | * Parameter map for {{nickname}}. 11 | */ 12 | export interface PartialParamMap { 13 | {{#allParams}} 14 | {{#description}} 15 | /** 16 | * {{{description}}} 17 | */ 18 | {{/description}} 19 | {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}; 20 | {{/allParams}} 21 | } 22 | 23 | /** 24 | * Enumeration of all parameters for {{nickname}}. 25 | */ 26 | export enum Parameters { 27 | {{#allParams}} 28 | {{#description}} 29 | /** 30 | * {{{description}}} 31 | */ 32 | {{/description}} 33 | {{paramName}} = '{{paramName}}'{{^-last}},{{/-last}} 34 | {{/allParams}} 35 | } 36 | 37 | /** 38 | * A map of tuples with error name and `ValidatorFn` for each parameter of {{nickname}} 39 | * that does not have an own model. 40 | */ 41 | export const ParamValidators: {[K in keyof {{operationIdCamelCase}}.PartialParamMap]?: [string, ValidatorFn][]} = { 42 | {{#allParams}} 43 | {{^isModel}} 44 | {{paramName}}: [ 45 | {{#required}} 46 | ['required', Validators.required], 47 | {{/required}} 48 | {{#hasValidation}} 49 | {{#minimum}} 50 | ['min', Validators.min({{minimum}})], 51 | {{/minimum}} 52 | {{#maximum}} 53 | ['max', Validators.max({{maximum}})], 54 | {{/maximum}} 55 | {{#minLength}} 56 | ['minlength', Validators.minLength({{minLength}})], 57 | {{/minLength}} 58 | {{#maxLength}} 59 | ['maxlength', Validators.maxLength({{maxLength}})], 60 | {{/maxLength}} 61 | {{#pattern}} 62 | ['pattern', Validators.pattern({{pattern}})], 63 | {{/pattern}} 64 | {{/hasValidation}} 65 | ], 66 | {{/isModel}} 67 | {{/allParams}} 68 | }; 69 | } 70 | 71 | {{/operation}} 72 | -------------------------------------------------------------------------------- /src/mustache/configuration.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | {{^useRxJS6}} 5 | import { Observable } from 'rxjs/Observable'; 6 | {{/useRxJS6}} 7 | {{#useRxJS6}} 8 | import { Observable } from 'rxjs'; 9 | {{/useRxJS6}} 10 | 11 | export interface ConfigurationParameters { 12 | apiKeys?: {[ key: string ]: string}; 13 | username?: string; 14 | password?: string; 15 | accessToken?: string | (() => string); 16 | errorHandler?: (err: any, operationName: string) => Observable; 17 | beforeHandler?: (operationName: string, method: string) => void; 18 | afterHandler?: (operationName: string, method: string, result: any) => void; 19 | defaultHeaderValue?: (headerName: string, operationName: string) => string; 20 | basePath?: string; 21 | withCredentials?: boolean; 22 | } 23 | 24 | export class Configuration { 25 | apiKeys?: {[ key: string ]: string}; 26 | username?: string; 27 | password?: string; 28 | accessToken?: string | (() => string); 29 | errorHandler?: (err: any, operationName: string) => Observable; 30 | beforeHandler?: (operationName: string, method: string) => void; 31 | afterHandler?: (operationName: string, method: string, result: any) => void; 32 | defaultHeaderValue?: (headerName: string, operationName: string) => string; 33 | basePath?: string; 34 | withCredentials?: boolean; 35 | 36 | constructor(configurationParameters: ConfigurationParameters = {}) { 37 | this.apiKeys = configurationParameters.apiKeys; 38 | this.username = configurationParameters.username; 39 | this.password = configurationParameters.password; 40 | this.accessToken = configurationParameters.accessToken; 41 | this.errorHandler = configurationParameters.errorHandler; 42 | this.beforeHandler = configurationParameters.beforeHandler; 43 | this.afterHandler = configurationParameters.afterHandler; 44 | this.defaultHeaderValue = configurationParameters.defaultHeaderValue; 45 | this.basePath = configurationParameters.basePath; 46 | this.withCredentials = configurationParameters.withCredentials; 47 | } 48 | 49 | /** 50 | * Select the correct content-type to use for a request. 51 | * Uses {@link Configuration#isJsonMime} to determine the correct content-type. 52 | * If no content type is found return the first found type if the contentTypes is not empty 53 | * @param contentTypes - the array of content types that are available for selection 54 | * @returns the selected content-type or undefined if no selection could be made. 55 | */ 56 | public selectHeaderContentType (contentTypes: string[]): string | undefined { 57 | if (contentTypes.length === 0) { 58 | return undefined; 59 | } 60 | 61 | let type = contentTypes.find(x => this.isJsonMime(x)); 62 | if (type === undefined) { 63 | return contentTypes[0]; 64 | } 65 | return type; 66 | } 67 | 68 | /** 69 | * Select the correct accept content-type to use for a request. 70 | * Uses {@link Configuration#isJsonMime} to determine the correct accept content-type. 71 | * If no content type is found return the first found type if the contentTypes is not empty 72 | * @param accepts - the array of content types that are available for selection. 73 | * @returns the selected content-type or undefined if no selection could be made. 74 | */ 75 | public selectHeaderAccept(accepts: string[]): string | undefined { 76 | if (accepts.length === 0) { 77 | return undefined; 78 | } 79 | 80 | let type = accepts.find(x => this.isJsonMime(x)); 81 | if (type === undefined) { 82 | return accepts[0]; 83 | } 84 | return type; 85 | } 86 | 87 | /** 88 | * Check if the given MIME is a JSON MIME. 89 | * JSON MIME examples: 90 | * application/json 91 | * application/json; charset=UTF8 92 | * APPLICATION/JSON 93 | * application/vnd.company+json 94 | * @param mime - MIME (Multipurpose Internet Mail Extensions) 95 | * @return True if the given MIME is JSON, false otherwise. 96 | */ 97 | public isJsonMime(mime: string): boolean { 98 | const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); 99 | return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/mustache/licenseInfo.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | /* 5 | * Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH 6 | * Riesaer Str. 5, 01129 Dresden 7 | * All rights reserved. 8 | * 9 | * {{{appName}}} 10 | * {{{appDescription}}} 11 | * 12 | * {{#version}}OpenAPI spec version: {{{version}}}{{/version}} 13 | * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} 14 | * 15 | * NOTE: This class is auto generated by the openapi-typescript-angular-generator. 16 | * https://github.com/T-Systems-MMS/openapi-typescript-angular-generator 17 | * Do not edit the class manually. 18 | */ 19 | -------------------------------------------------------------------------------- /src/mustache/model.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | {{>licenseInfo}} 5 | {{#models}} 6 | {{#model}} 7 | {{>modelValidatorImports}} 8 | {{#tsImports}} 9 | import { {{classname}} } from './{{filename}}'; 10 | {{/tsImports}} 11 | 12 | 13 | {{#description}} 14 | /** 15 | * {{{description}}} 16 | */ 17 | {{/description}} 18 | {{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#isAlias}}{{>modelAlias}}{{/isAlias}}{{^isAlias}}{{#taggedUnions}}{{>modelTaggedUnion}}{{/taggedUnions}}{{^taggedUnions}}{{>modelGeneric}}{{/taggedUnions}}{{/isAlias}}{{/isEnum}} 19 | {{/model}} 20 | {{/models}} 21 | -------------------------------------------------------------------------------- /src/mustache/modelGeneric.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | export interface {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ {{>modelGenericAdditionalProperties}} 5 | {{#vars}} 6 | {{#description}} 7 | /** 8 | * {{{description}}} 9 | */ 10 | {{/description}} 11 | {{#isReadOnly}}readonly {{/isReadOnly}}{{{name}}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}; 12 | {{/vars}} 13 | } 14 | 15 | {{>modelGenericEnums}} 16 | -------------------------------------------------------------------------------- /src/mustache/modelGenericEnums.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | /** 5 | * Namespace for property- and property-value-enumerations of {{{classname}}}. 6 | */ 7 | export namespace {{classname}} { 8 | /** 9 | * All properties of {{{classname}}}. 10 | */ 11 | export enum Properties { 12 | {{#vars}} 13 | {{#description}} 14 | /** 15 | * {{{description}}} 16 | */ 17 | {{/description}} 18 | {{name}} = '{{name}}'{{^-last}},{{/-last}} 19 | {{/vars}} 20 | } 21 | {{#vars}} 22 | {{#isEnum}} 23 | 24 | /** 25 | * All possible values of {{{name}}}. 26 | */ 27 | export enum {{enumName}} { 28 | {{#allowableValues}} 29 | {{#enumVars}} 30 | {{name}} = {{{value}}}{{^-last}},{{/-last}} 31 | {{/enumVars}} 32 | {{/allowableValues}} 33 | } 34 | {{/isEnum}} 35 | {{/vars}} 36 | 37 | {{>modelGenericValidators}} 38 | } 39 | -------------------------------------------------------------------------------- /src/mustache/modelGenericValidators.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | /** 5 | * A map of tuples with error name and `ValidatorFn` for each property of {{classname}}. 6 | */ 7 | export const ModelValidators: {[K in keyof {{classname}}]: [string, ValidatorFn][]} = { 8 | {{#vars}} 9 | {{name}}: [ 10 | {{#required}} 11 | ['required', Validators.required], 12 | {{/required}} 13 | {{#hasValidation}} 14 | {{#minimum}} 15 | ['min', Validators.min({{minimum}})], 16 | {{/minimum}} 17 | {{#maximum}} 18 | ['max', Validators.max({{maximum}})], 19 | {{/maximum}} 20 | {{#minLength}} 21 | ['minlength', Validators.minLength({{minLength}})], 22 | {{/minLength}} 23 | {{#maxLength}} 24 | ['maxlength', Validators.maxLength({{maxLength}})], 25 | {{/maxLength}} 26 | {{#pattern}} 27 | ['pattern', Validators.pattern({{pattern}})], 28 | {{/pattern}} 29 | {{/hasValidation}} 30 | ], 31 | {{/vars}} 32 | }; 33 | 34 | /** 35 | * The FormControlFactory for {{classname}}. 36 | */ 37 | export class FormControlFactory extends BaseFormControlFactory<{{classname}}> { 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param model An existing model for {{classname}}. 43 | * If given, all form-controls of the factory automatically have the value of this model. If this 44 | * model is not given, all values are `null`. 45 | */ 46 | constructor( 47 | model: {{classname}} = { 48 | {{#vars}} 49 | {{name}}: null, 50 | {{/vars}} 51 | } 52 | ) { 53 | super(model, {{classname}}.ModelValidators); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/mustache/modelValidatorImports.mustache: -------------------------------------------------------------------------------- 1 | {{! Copyright(c) 1995 - 2018 T-Systems Multimedia Solutions GmbH }} 2 | {{! Riesaer Str. 5, 01129 Dresden }} 3 | {{! All rights reserved. }} 4 | import { ValidatorFn, Validators } from '@angular/forms'; 5 | import { BaseFormControlFactory } from 'openapi-typescript-angular-generator'; 6 | -------------------------------------------------------------------------------- /tsconfig-es2015.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es2015" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "esnext", 5 | "lib": ["es2018", "es2017", "es7", "es6", "dom"], 6 | "declaration": true, 7 | "declarationDir": "dist", 8 | "outDir": "dist", 9 | "strict": true, 10 | "strictPropertyInitialization": false, 11 | "noImplicitAny": false, 12 | "alwaysStrict": true, 13 | "esModuleInterop": true, 14 | "moduleResolution": "node" 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | "dist", 19 | "**/*.spec*" 20 | ] 21 | } 22 | --------------------------------------------------------------------------------