├── .all-contributorsrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT ├── DCO ├── LICENSE ├── NOTICE ├── README.md ├── angular.json ├── lerna.json ├── package.json ├── projects └── ngx-structurals │ ├── CHANGELOG.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── ngx-alias │ │ │ ├── ngx-alias.directive.spec.ts │ │ │ ├── ngx-alias.directive.ts │ │ │ ├── ngx-alias.module.ts │ │ │ └── public-api.ts │ │ ├── ngx-repeat │ │ │ ├── ngx-repeat.directive.spec.ts │ │ │ ├── ngx-repeat.directive.ts │ │ │ ├── ngx-repeat.module.ts │ │ │ └── public-api.ts │ │ ├── ngx-structurals.module.ts │ │ ├── ngx-subscribe │ │ │ ├── ngx-subscribe.directive.spec.ts │ │ │ ├── ngx-subscribe.directive.ts │ │ │ ├── ngx-subscribe.module.ts │ │ │ └── public-api.ts │ │ └── ngx-template-context │ │ │ ├── ngx-template-context.directive.spec.ts │ │ │ ├── ngx-template-context.directive.ts │ │ │ ├── ngx-template-context.module.ts │ │ │ └── public-api.ts │ ├── public-api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ └── tslint.json ├── scripts └── release.sh ├── tsconfig.json ├── tslint.json └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "Airblader", 10 | "name": "Ingo Bürk", 11 | "avatar_url": "https://avatars3.githubusercontent.com/u/2392216?v=4", 12 | "profile": "https://github.com/Airblader", 13 | "contributions": [ 14 | "code" 15 | ] 16 | }, 17 | { 18 | "login": "kentkwee", 19 | "name": "kentkwee", 20 | "avatar_url": "https://avatars.githubusercontent.com/u/79371980?v=4", 21 | "profile": "https://github.com/kentkwee", 22 | "contributions": [ 23 | "code" 24 | ] 25 | }, 26 | { 27 | "login": "snebjorn", 28 | "name": "snebjorn", 29 | "avatar_url": "https://avatars.githubusercontent.com/u/1266245?v=4", 30 | "profile": "https://github.com/snebjorn", 31 | "contributions": [ 32 | "bug", 33 | "code" 34 | ] 35 | } 36 | ], 37 | "contributorsPerLine": 7, 38 | "projectName": "ngx-structurals", 39 | "projectOwner": "TNG", 40 | "repoType": "github", 41 | "repoHost": "https://github.com", 42 | "skipCi": true 43 | } 44 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = false 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | **/dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | 48 | *.log 49 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: xenial 3 | node_js: 4 | - lts/* 5 | branches: 6 | only: 7 | - master 8 | #notifications: 9 | # email: 10 | # on_success: change 11 | # on_failure: always 12 | # recipients: 13 | script: 14 | - yarn run lint 15 | - yarn run build 16 | - yarn run test --watch=false 17 | after_success: 18 | - yarn run codecov 19 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at coc@ngqp.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 Ingo Bürk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ngqp 2 | Copyright 2019 Ingo Bürk 3 | 4 | This product includes software developed at 5 | TNG Technology Consulting GmbH (https://www.tngtech.com/). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/TNG/ngx-structurals.svg?branch=master)](https://travis-ci.com/TNG/ngx-structurals) 2 | [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors) 3 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-green.svg)](https://conventionalcommits.org) 4 | 5 | # ngx-structurals 6 | 7 | Structural utility directives for Angular. 8 | 9 | The most commonly used [structural directives](https://angular.io/guide/structural-directives) are `*ngIf` and `*ngFor` 10 | since they are shipped as built-ins with Angular. However, we are not limited to these and can implement our own. This 11 | is an often under-appreciate, powerful concept baked into Angular. ngx-structural aims to provide structural directives 12 | which may be useful in any Angular project. 13 | 14 | ## How to get it? 15 | 16 | Simply install ngx-structurals with the package manager of your choice: 17 | 18 | ``` 19 | npm install --save @tngtech/ngx-structurals 20 | yarn add @tngtech/ngx-structurals 21 | ``` 22 | 23 | You can now import `NgxStructuralsModule` into your application to get access to the directives. 24 | 25 | ## How to use it? 26 | 27 | 28 | * [*ngxSubscribe](#ngxSubscribe) 29 | * [*ngxRepeat](#ngxRepeat) 30 | * [*ngxAlias](#ngxAlias) 31 | * [ngxTemplateContext](#ngxTemplateContext) 32 | 33 | 34 | ### *ngxSubscribe 35 | 36 | *TL;DR* 37 | 38 | ``` 39 | 40 | 41 | Emitted: {{ value }} 42 | 43 | ``` 44 | 45 | You can subscribe to an observable directly from the template using `*ngxSubscribe`. While you can achieve the same thing using `*ngIf="data$ | async as data"`, 46 | the latter has a couple of disadvantages: 47 | 1. It fails if `data$` emits falsy values such as `0` or `null`. 48 | 2. There is no way to access error or completion information of the observable. 49 | 3. Rendering is deferred until the observable actually emits. 50 | 51 | With `*ngxSubscribe` all of these points are addressed. Through the template context you have access to all relevant information: 52 | 53 | ``` 54 | 55 |

Number of emitted values: {{ count }}

56 |

Last emitted value: {{ value }}

57 |

Error: {{ error }}

58 |

Completed

59 |
60 | ``` 61 | 62 | By default, the template on which the directive is applied is used. However, you can also specify different templates for different scenarios: 63 | 64 | ``` 65 | 66 | 67 | 68 | Value: {{ value }} 69 | Waiting for first emission… 70 | Error: {{ error }} 71 | Completed 72 | ``` 73 | 74 | This can be particularly useful for showing loading and error state. 75 | 76 | ### *ngxRepeat 77 | 78 | *TL;DR* 79 | 80 | ``` 81 | 84 | ``` 85 | 86 | Renders the given template as many times as specified. This is equivalent of using `*ngFor` on an array of that length, but avoids having to initialize such 87 | an array if you only know the number of items you want to render. 88 | 89 | You can also access similar context information as with `*ngFor`: 90 | 91 | ``` 92 | 93 |

Start

94 |

Item {{ index }} of {{ count }} is even={{ even }}, odd={{ odd }}

95 |

End

96 |
97 | ``` 98 | 99 | ### *ngxAlias 100 | 101 | *TL;DR* 102 | 103 | ``` 104 | {{ data }} 105 | ``` 106 | 107 | Simply renders the given template, but allows aliasing a complex expression to a local template input variable. This is similar to using `*ngIf` for the same job, 108 | but avoids the issues arising from falsy values which would cause the template not to render. 109 | 110 | ### ngxTemplateContext 111 | 112 | *TL;DR* 113 | 114 | ``` 115 | {{ data.prop }} 116 | ``` 117 | 118 | Defines the type of the context based on some object. This utility is a workaround for [#28731](https://github.com/angular/angular/issues/28731) as the context of a template is untyped. The context passed into it is some (possibly empty) object with the appropriate type, e.g. 119 | 120 | ``` 121 | context?: MyContextType; 122 | ``` 123 | 124 | Note that this directive only types the context of the directive, but cannot enforce that the context passed to the template actually matches that type. 125 | 126 | ## Contributors ✨ 127 | 128 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |

Ingo Bürk

💻

kentkwee

💻

snebjorn

🐛 💻
140 | 141 | 142 | 143 | 144 | 145 | 146 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 147 | 148 | --- 149 | 150 | [MIT License][license] 151 | 152 | [license]: https://www.github.com/TNG/ngx-structurals/blob/master/LICENSE 153 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-structurals": { 7 | "projectType": "library", 8 | "root": "projects/ngx-structurals", 9 | "sourceRoot": "projects/ngx-structurals/src", 10 | "prefix": "ngx", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-ng-packagr:build", 14 | "options": { 15 | "tsConfig": "projects/ngx-structurals/tsconfig.lib.json", 16 | "project": "projects/ngx-structurals/ng-package.json" 17 | }, 18 | "configurations": { 19 | "production": { 20 | "tsConfig": "projects/ngx-structurals/tsconfig.lib.prod.json" 21 | } 22 | } 23 | }, 24 | "test": { 25 | "builder": "@angular-devkit/build-angular:karma", 26 | "options": { 27 | "main": "projects/ngx-structurals/src/test.ts", 28 | "tsConfig": "projects/ngx-structurals/tsconfig.spec.json", 29 | "karmaConfig": "projects/ngx-structurals/karma.conf.js" 30 | } 31 | }, 32 | "lint": { 33 | "builder": "@angular-devkit/build-angular:tslint", 34 | "options": { 35 | "tsConfig": [ 36 | "projects/ngx-structurals/tsconfig.lib.json", 37 | "projects/ngx-structurals/tsconfig.spec.json" 38 | ], 39 | "exclude": [ 40 | "**/node_modules/**" 41 | ] 42 | } 43 | } 44 | } 45 | } 46 | }, 47 | "defaultProject": "ngx-structurals" 48 | } -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "projects/*" 4 | ], 5 | "version": "independent", 6 | "command": { 7 | "publish": { 8 | "conventionalCommits": true, 9 | "message": "chore: new release" 10 | }, 11 | "version": { 12 | "push": false 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tngtech/ngx-structurals", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build --prod", 8 | "release": "scripts/release.sh", 9 | "test": "ng test --browsers=ChromeHeadless", 10 | "lint": "ng lint", 11 | "codecov": "codecov", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "~10.2.0", 17 | "@angular/common": "~10.2.0", 18 | "@angular/compiler": "~10.2.0", 19 | "@angular/core": "~10.2.0", 20 | "@angular/forms": "~10.2.0", 21 | "@angular/platform-browser": "~10.2.0", 22 | "@angular/platform-browser-dynamic": "~10.2.0", 23 | "@angular/router": "~10.2.0", 24 | "rxjs": "~6.6.3", 25 | "tslib": "^2.0.0", 26 | "zone.js": "~0.10.2" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.1001.7", 30 | "@angular-devkit/build-ng-packagr": "~0.1001.7", 31 | "@angular/cli": "~10.1.7", 32 | "@angular/compiler-cli": "~10.2.0", 33 | "@angular/language-service": "~10.2.0", 34 | "@commitlint/cli": "^11.0.0", 35 | "@commitlint/config-conventional": "^11.0.0", 36 | "@types/jasmine": "~3.5.14", 37 | "@types/jasminewd2": "~2.0.3", 38 | "@types/node": "^12.11.1", 39 | "codecov": "^3.8.0", 40 | "codelyzer": "^6.0.1", 41 | "husky": "^4.3.0", 42 | "jasmine-core": "~3.5.0", 43 | "jasmine-spec-reporter": "~5.0.0", 44 | "karma": "~6.3.14", 45 | "karma-chrome-launcher": "~3.1.0", 46 | "karma-coverage-istanbul-reporter": "~3.0.2", 47 | "karma-jasmine": "~4.0.0", 48 | "karma-jasmine-html-reporter": "^1.5.0", 49 | "lerna": "^3.22.1", 50 | "ng-packagr": "^10.1.0", 51 | "protractor": "~7.0.0", 52 | "ts-node": "~9.0.0", 53 | "tslint": "~6.1.0", 54 | "typescript": "~4.0.3" 55 | }, 56 | "husky": { 57 | "hooks": { 58 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 59 | } 60 | }, 61 | "commitlint": { 62 | "extends": [ 63 | "@commitlint/config-conventional" 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /projects/ngx-structurals/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.2.2](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.2.1...@tngtech/ngx-structurals@1.2.2) (2021-08-13) 7 | 8 | **Note:** Version bump only for package @tngtech/ngx-structurals 9 | 10 | 11 | 12 | 13 | 14 | ## [1.2.1](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.2.0...@tngtech/ngx-structurals@1.2.1) (2021-02-23) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * **ngx-alias:** fixes incorrect `T | null` in AsyncPipe bool comparison ([463f04b](https://github.com/TNG/ngx-structurals/commit/463f04b5ee4efb1afaaed96cb5d9d2f721ad8d1d)), closes [#37](https://github.com/TNG/ngx-structurals/issues/37) 20 | 21 | 22 | 23 | 24 | 25 | # [1.2.0](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.1.3...@tngtech/ngx-structurals@1.2.0) (2021-02-21) 26 | 27 | 28 | ### Features 29 | 30 | * add ngxTemplateDirective for providing context in ng-template body tags ([5578c10](https://github.com/TNG/ngx-structurals/commit/5578c1037536ac3bc47cd43ee7a45927abbc0226)) 31 | 32 | 33 | 34 | 35 | 36 | ## [1.1.3](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.1.2...@tngtech/ngx-structurals@1.1.3) (2020-10-22) 37 | 38 | **Note:** Version bump only for package @tngtech/ngx-structurals 39 | 40 | 41 | 42 | 43 | 44 | ## [1.1.2](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.1.1...@tngtech/ngx-structurals@1.1.2) (2020-07-30) 45 | 46 | **Note:** Version bump only for package @tngtech/ngx-structurals 47 | 48 | 49 | 50 | 51 | 52 | ## [1.1.1](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.1.0...@tngtech/ngx-structurals@1.1.1) (2020-04-22) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * **ngx-subscribe:** ensure Ivy's type-checker is happy ([a87aa08](https://github.com/TNG/ngx-structurals/commit/a87aa08d47530ba8066cde9abd61b60a8f402e05)) 58 | 59 | 60 | 61 | 62 | 63 | # [1.1.0](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.0.1...@tngtech/ngx-structurals@1.1.0) (2020-01-24) 64 | 65 | 66 | ### Features 67 | 68 | * support Angular 9 ([1eecffa](https://github.com/TNG/ngx-structurals/commit/1eecffa7a0df321ee62dc6a3ffe971c203be9323)), closes [#11](https://github.com/TNG/ngx-structurals/issues/11) 69 | 70 | 71 | 72 | 73 | 74 | ## [1.0.1](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@1.0.0...@tngtech/ngx-structurals@1.0.1) (2020-01-18) 75 | 76 | **Note:** Version bump only for package @tngtech/ngx-structurals 77 | 78 | 79 | 80 | 81 | 82 | # [1.0.0](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@0.3.1...@tngtech/ngx-structurals@1.0.0) (2020-01-10) 83 | 84 | 85 | ### chore 86 | 87 | * ignore me… ([f2f5fde](https://github.com/TNG/ngx-structurals/commit/f2f5fde94bc86d9a2ffbd89e2290eefff9cd52b5)) 88 | 89 | 90 | ### BREAKING CHANGES 91 | 92 | * release 1.0 93 | 94 | Signed-off-by: Ingo Bürk 95 | 96 | 97 | 98 | 99 | 100 | ## [0.3.1](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@0.2.2...@tngtech/ngx-structurals@0.3.1) (2020-01-10) 101 | 102 | **Note:** Version bump only for package @tngtech/ngx-structurals 103 | 104 | 105 | 106 | 107 | 108 | ## [0.2.2](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@0.2.1...@tngtech/ngx-structurals@0.2.2) (2020-01-10) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * export directives ([d071387](https://github.com/TNG/ngx-structurals/commit/d0713870ec2a1c88d3757ac2935389d1d6e8f0a4)) 114 | 115 | 116 | 117 | 118 | 119 | ## [0.2.1](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@0.1.2...@tngtech/ngx-structurals@0.2.1) (2020-01-10) 120 | 121 | 122 | ### Reverts 123 | 124 | * Revert "chore: manually bump" ([4881b2c](https://github.com/TNG/ngx-structurals/commit/4881b2c9c963796362ff84482a15731a659d0ca6)) 125 | 126 | 127 | 128 | 129 | 130 | ## [0.1.2](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@0.1.1...@tngtech/ngx-structurals@0.1.2) (2020-01-10) 131 | 132 | **Note:** Version bump only for package @tngtech/ngx-structurals 133 | 134 | 135 | 136 | 137 | 138 | ## [0.1.1](https://github.com/TNG/ngx-structurals/compare/@tngtech/ngx-structurals@0.1.0...@tngtech/ngx-structurals@0.1.1) (2020-01-10) 139 | 140 | **Note:** Version bump only for package @tngtech/ngx-structurals 141 | 142 | 143 | 144 | 145 | 146 | # 0.1.0 (2020-01-10) 147 | 148 | 149 | ### Bug Fixes 150 | 151 | * **ngx-alias:** only update context ([25b3106](https://github.com/TNG/ngx-structurals/commit/25b3106ff9beb3e04dd1a1f41b35554aa01a8d6f)) 152 | * **ngx-subscribe:** fix check for template ([c45c3db](https://github.com/TNG/ngx-structurals/commit/c45c3db6a09a28d776a1ef9241bd3b9449beffcf)) 153 | * **ngx-subscribe:** improve syntax ([0f5ab7b](https://github.com/TNG/ngx-structurals/commit/0f5ab7b57a9110f92f7cc366a67c01ae202a59a1)), closes [#9](https://github.com/TNG/ngx-structurals/issues/9) 154 | * **ngx-subscribe:** rename context class ([6ddd21e](https://github.com/TNG/ngx-structurals/commit/6ddd21ed0666aad86562a23f18d3c8e1a74a0866)) 155 | * **ngx-subscribe:** throw custom error if used incorrectly ([d8533b4](https://github.com/TNG/ngx-structurals/commit/d8533b46576b380de8c0607fcdfb8143a3177178)) 156 | 157 | 158 | ### Features 159 | 160 | * **ngx-alias:** added new directive ([703fc04](https://github.com/TNG/ngx-structurals/commit/703fc04fc09d617611e8d72b9ec77ce617b4925c)), closes [#8](https://github.com/TNG/ngx-structurals/issues/8) 161 | * **ngx-repeat:** added directive ([a503767](https://github.com/TNG/ngx-structurals/commit/a50376733bc32723246340b68ff849893c281d66)), closes [#2](https://github.com/TNG/ngx-structurals/issues/2) 162 | * **ngx-subscribe:** added initial implementation ([48c1142](https://github.com/TNG/ngx-structurals/commit/48c114207f1e1d21024fceaf6dfc697eb86741e5)) 163 | * added template context guards ([e628dd3](https://github.com/TNG/ngx-structurals/commit/e628dd331b3a0b2c6775dc1848133c566321933e)), closes [#7](https://github.com/TNG/ngx-structurals/issues/7) 164 | * enable strict settings ([1cad022](https://github.com/TNG/ngx-structurals/commit/1cad022d7bf2cb76e61db6cef97008fb0f093274)), closes [#6](https://github.com/TNG/ngx-structurals/issues/6) 165 | 166 | 167 | 168 | 169 | 170 | ## 0.0.2 (2019-11-10) 171 | 172 | **Note:** Version bump only for package ngx-structurals 173 | -------------------------------------------------------------------------------- /projects/ngx-structurals/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/ngx-structurals'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /projects/ngx-structurals/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-structurals", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-structurals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tngtech/ngx-structurals", 3 | "version": "1.2.2", 4 | "description": "Structural utility directives for Angular", 5 | "homepage": "https://www.github.com/TNG/ngx-structurals", 6 | "author": "Ingo Bürk", 7 | "license": "MIT", 8 | "bugs": "https://www.github.com/TNG/ngx-structurals/issues", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://www.github.com/TNG/ngx-structurals" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "directive", 16 | "structural-directive" 17 | ], 18 | "peerDependencies": { 19 | "@angular/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", 20 | "@angular/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0" 21 | }, 22 | "dependencies": { 23 | "tslib": "^2.0.0" 24 | }, 25 | "publishConfig": { 26 | "access": "public" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-alias/ngx-alias.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { NgxAliasDirective } from './ngx-alias.directive'; 4 | 5 | function setupComponent(component: new(...args: any[]) => T, targetSelector?: string): [ComponentFixture, () => HTMLElement] { 6 | TestBed.configureTestingModule({ 7 | declarations: [NgxAliasDirective, component], 8 | }); 9 | 10 | TestBed.compileComponents().then(); 11 | const fixture = TestBed.createComponent(component); 12 | fixture.detectChanges(); 13 | 14 | return [ 15 | fixture, 16 | () => targetSelector 17 | ? (fixture.nativeElement as HTMLElement).querySelector(targetSelector) as HTMLElement 18 | : fixture.nativeElement as HTMLElement, 19 | ]; 20 | } 21 | 22 | describe('ngxAlias', () => { 23 | it('renders the template with an aliased property', () => { 24 | @Component({ 25 | template: `{{ count }}`, 26 | }) 27 | class TestComponent { 28 | public data: number[] = [1, 2, 3]; 29 | } 30 | 31 | const [fixture, getTarget] = setupComponent(TestComponent); 32 | expect(getTarget().textContent.trim()).toBe('3'); 33 | 34 | // In-place change 35 | fixture.componentInstance.data.push(4); 36 | fixture.detectChanges(); 37 | expect(getTarget().textContent.trim()).toBe('4'); 38 | 39 | // Reference change 40 | fixture.componentInstance.data = [1, 2]; 41 | fixture.detectChanges(); 42 | expect(getTarget().textContent.trim()).toBe('2'); 43 | }); 44 | 45 | it('renders the template for falsy values', () => { 46 | @Component({ 47 | template: `{{ count }}`, 48 | }) 49 | class TestComponent { 50 | public data: number[] = []; 51 | } 52 | 53 | const [fixture, getTarget] = setupComponent(TestComponent); 54 | expect(getTarget().textContent.trim()).toBe('0'); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-alias/ngx-alias.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnInit, Optional, TemplateRef, ViewContainerRef } from '@angular/core'; 2 | 3 | /** 4 | * Context provided when using the [ngxAlias] directive. 5 | * 6 | * @publicApi 7 | */ 8 | export class NgxAliasContext { 9 | /** 10 | * Value of the aliased expression. 11 | * 12 | * This simply mirrors the value of the expression given to ngxAlias. 13 | * 14 | * @publicApi 15 | */ 16 | // tslint:disable-next-line: no-non-null-assertion justification https://github.com/angular/vscode-ng-language-service/issues/1137 17 | public ngxAlias: T = null!; 18 | 19 | /** 20 | * Synonym for {@link ngxAlias}. 21 | * 22 | * @publicApi 23 | */ 24 | public get $implicit(): T { 25 | return this.ngxAlias; 26 | } 27 | } 28 | 29 | /** 30 | * Aliases an expression for a template. 31 | * 32 | * You can use [ngxAlias] to unconditionally render a given template, but aliasing a potentially complex expression to a simpler 33 | * template input variable. The template will always be rendered, even if the value of the expression is falsy. 34 | * 35 | * @publicApi 36 | */ 37 | @Directive({ 38 | selector: '[ngxAlias]', 39 | }) 40 | export class NgxAliasDirective implements OnInit { 41 | 42 | private readonly context = new NgxAliasContext(); 43 | 44 | constructor( 45 | private readonly viewContainer: ViewContainerRef, 46 | @Optional() private readonly templateRef: TemplateRef, 47 | ) { 48 | if (!this.templateRef) { 49 | throw new Error(`[ngxAlias] can only be used as a structural directive or on an ng-template.`); 50 | } 51 | } 52 | 53 | /** @internal */ 54 | public static ngTemplateContextGuard(dir: NgxAliasDirective, ctx: unknown): ctx is NgxAliasContext { 55 | return true; 56 | } 57 | 58 | public ngOnInit(): void { 59 | this.viewContainer.createEmbeddedView(this.templateRef, this.context); 60 | } 61 | 62 | /** 63 | * Expression to alias. 64 | * 65 | * @publicApi 66 | */ 67 | @Input() 68 | public set ngxAlias(expression: T) { 69 | this.context.ngxAlias = expression; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-alias/ngx-alias.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgxAliasDirective } from './ngx-alias.directive'; 4 | 5 | @NgModule({ 6 | declarations: [NgxAliasDirective], 7 | exports: [NgxAliasDirective], 8 | imports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class NgxAliasModule { 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-alias/public-api.ts: -------------------------------------------------------------------------------- 1 | export { NgxAliasModule } from './ngx-alias.module'; 2 | export { NgxAliasDirective } from './ngx-alias.directive'; -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-repeat/ngx-repeat.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { NgxRepeatDirective } from './ngx-repeat.directive'; 4 | 5 | function setupComponent(component: new(...args: any[]) => T, targetSelector?: string): [ComponentFixture, () => HTMLElement] { 6 | TestBed.configureTestingModule({ 7 | declarations: [NgxRepeatDirective, component], 8 | }); 9 | 10 | TestBed.compileComponents().then(); 11 | const fixture = TestBed.createComponent(component); 12 | fixture.detectChanges(); 13 | 14 | return [ 15 | fixture, 16 | () => targetSelector 17 | ? (fixture.nativeElement as HTMLElement).querySelector(targetSelector) as HTMLElement 18 | : fixture.nativeElement as HTMLElement, 19 | ]; 20 | } 21 | 22 | describe('ngxRepeat', () => { 23 | it('renders nothing if the count is zero', () => { 24 | @Component({ 25 | template: `Test`, 26 | }) 27 | class TestComponent { 28 | } 29 | 30 | const [fixture, getTarget] = setupComponent(TestComponent); 31 | expect(getTarget().textContent.trim()).toBe(''); 32 | }); 33 | 34 | it('renders the template as many times as defined', () => { 35 | @Component({ 36 | template: `Test`, 37 | }) 38 | class TestComponent { 39 | } 40 | 41 | const [fixture, getTarget] = setupComponent(TestComponent); 42 | expect(getTarget().textContent.trim()).toBe('TestTestTest'); 43 | }); 44 | 45 | it('sets the current index in the context', () => { 46 | @Component({ 47 | template: ` 48 | {{ index }}| 49 | `, 50 | }) 51 | class TestComponent { 52 | } 53 | 54 | const [fixture, getTarget] = setupComponent(TestComponent); 55 | expect(getTarget().textContent.trim()).toBe('0|1|2|'); 56 | }); 57 | 58 | it('sets the count in the context', () => { 59 | @Component({ 60 | template: ` 61 | {{ count }}| 62 | `, 63 | }) 64 | class TestComponent { 65 | } 66 | 67 | const [fixture, getTarget] = setupComponent(TestComponent); 68 | expect(getTarget().textContent.trim()).toBe('3|3|3|'); 69 | }); 70 | 71 | it('sets even and odd in the context', () => { 72 | @Component({ 73 | template: ` 74 | {{ even }}|{{ odd }}| 75 | `, 76 | }) 77 | class TestComponent { 78 | } 79 | 80 | const [fixture, getTarget] = setupComponent(TestComponent); 81 | expect(getTarget().textContent.trim()).toBe('true|false|false|true|true|false|'); 82 | }); 83 | 84 | it('sets first and last in the context', () => { 85 | @Component({ 86 | template: ` 87 | {{ first }}|{{ last }}| 88 | `, 89 | }) 90 | class TestComponent { 91 | } 92 | 93 | const [fixture, getTarget] = setupComponent(TestComponent); 94 | expect(getTarget().textContent.trim()).toBe('true|false|false|false|false|true|'); 95 | }); 96 | 97 | it('updates the context if the repetition count decreases', () => { 98 | @Component({ 99 | template: ` 100 | {{ count }}| 101 | `, 102 | }) 103 | class TestComponent { 104 | public total = 3; 105 | } 106 | 107 | const [fixture, getTarget] = setupComponent(TestComponent); 108 | expect(getTarget().textContent.trim()).toBe('3|3|3|'); 109 | 110 | fixture.componentInstance.total = 2; 111 | fixture.detectChanges(); 112 | expect(getTarget().textContent.trim()).toBe('2|2|'); 113 | }); 114 | 115 | it('updates the context if the repetition count increases', () => { 116 | @Component({ 117 | template: ` 118 | {{ count }}|{{ last }}| 119 | `, 120 | }) 121 | class TestComponent { 122 | public total = 2; 123 | } 124 | 125 | const [fixture, getTarget] = setupComponent(TestComponent); 126 | expect(getTarget().textContent.trim()).toBe('2|false|2|true|'); 127 | 128 | fixture.componentInstance.total = 3; 129 | fixture.detectChanges(); 130 | expect(getTarget().textContent.trim()).toBe('3|false|3|false|3|true|'); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-repeat/ngx-repeat.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, EmbeddedViewRef, Input, Optional, TemplateRef, ViewContainerRef } from '@angular/core'; 2 | 3 | /** 4 | * Context provided when using the [ngxRepeat] directive. 5 | * 6 | * @publicApi 7 | */ 8 | export class NgxRepeatContext { 9 | /** 10 | * Index of the current template. 11 | * 12 | * This holds the index of the n-th template being currently rendered, i.e., if the template is repeated three times, this value 13 | * is in the range 0–2. 14 | * 15 | * @publicApi 16 | */ 17 | public $implicit = 0; 18 | /** 19 | * Total number of repetitions. 20 | * 21 | * Represents the count given to the directive, i.e. how many instances of the template will be rendered. 22 | */ 23 | public count = 0; 24 | 25 | /** 26 | * Whether the current index is even. 27 | * 28 | * Whether the current index as described in {@link $implicit} is an even number. 29 | * 30 | * @publicApi 31 | */ 32 | public get even(): boolean { 33 | return this.$implicit % 2 === 0; 34 | } 35 | 36 | /** 37 | * Whether the current index is odd. 38 | * 39 | * Whether the current index as described in {@link $implicit} is an odd number. 40 | * 41 | * @publicApi 42 | */ 43 | public get odd(): boolean { 44 | return this.$implicit % 2 !== 0; 45 | } 46 | 47 | /** 48 | * Whether this template instance is the first one. 49 | * 50 | * This is {@code true} only for the first template instance rendered. 51 | * 52 | * @publicApi 53 | */ 54 | public get first(): boolean { 55 | return this.$implicit === 0; 56 | } 57 | 58 | /** 59 | * Whether this template instance is the last one. 60 | * 61 | * This is {@code true} only for the last template instance rendered. 62 | * 63 | * @publicApi 64 | */ 65 | public get last(): boolean { 66 | return this.$implicit + 1 === this.count; 67 | } 68 | } 69 | 70 | /** 71 | * Repeats a template a given number of times. 72 | * 73 | * You can use [ngxRepeat] to render a template a specific number of times. This is similar to using {@code *ngFor} on an array of that 74 | * length, but avoids having to create such an array. The directive also exposes similar and useful context properties, see 75 | * {@link NgxRepeatContext}. 76 | * 77 | * @publicApi 78 | */ 79 | @Directive({ 80 | selector: '[ngxRepeat]', 81 | }) 82 | export class NgxRepeatDirective { 83 | 84 | constructor( 85 | private readonly viewContainer: ViewContainerRef, 86 | @Optional() private readonly templateRef: TemplateRef, 87 | ) { 88 | if (!this.templateRef) { 89 | throw new Error(`[ngxRepeat] can only be used as a structural directive or on an ng-template.`); 90 | } 91 | } 92 | 93 | /** @internal */ 94 | public static ngTemplateContextGuard(dir: NgxRepeatDirective, ctx: unknown): ctx is NgxRepeatContext { 95 | return true; 96 | } 97 | 98 | /** 99 | * Defines how often the template should be rendered. 100 | * 101 | * @publicApi 102 | */ 103 | @Input() 104 | public set ngxRepeat(count: number) { 105 | this.updateView(Number(count)); 106 | } 107 | 108 | private updateView(count: number): void { 109 | const current = this.viewContainer.length; 110 | if (current === count) { 111 | return; 112 | } 113 | 114 | for (let j = 0; j < current; j++) { 115 | const viewRef = this.viewContainer.get(j) as EmbeddedViewRef; 116 | viewRef.context.count = count; 117 | } 118 | 119 | if (current > count) { 120 | for (let i = count; i < current; i++) { 121 | this.viewContainer.remove(i); 122 | } 123 | } else { 124 | for (let i = current; i < count; i++) { 125 | const context = new NgxRepeatContext(); 126 | context.$implicit = i; 127 | context.count = count; 128 | 129 | this.viewContainer.createEmbeddedView(this.templateRef, context); 130 | } 131 | } 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-repeat/ngx-repeat.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgxRepeatDirective } from './ngx-repeat.directive'; 4 | 5 | @NgModule({ 6 | declarations: [NgxRepeatDirective], 7 | exports: [NgxRepeatDirective], 8 | imports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class NgxRepeatModule { 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-repeat/public-api.ts: -------------------------------------------------------------------------------- 1 | export { NgxRepeatModule } from './ngx-repeat.module'; 2 | export { NgxRepeatDirective } from './ngx-repeat.directive'; -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-structurals.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { NgxSubscribeModule } from './ngx-subscribe/ngx-subscribe.module'; 3 | import { NgxRepeatModule } from './ngx-repeat/ngx-repeat.module'; 4 | import { NgxAliasModule } from './ngx-alias/ngx-alias.module'; 5 | import { NgxTemplateContextModule } from './ngx-template-context/ngx-template-context.module'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | NgxSubscribeModule, 10 | NgxRepeatModule, 11 | NgxAliasModule, 12 | NgxTemplateContextModule, 13 | ], 14 | exports: [ 15 | NgxSubscribeModule, 16 | NgxRepeatModule, 17 | NgxAliasModule, 18 | NgxTemplateContextModule, 19 | ], 20 | }) 21 | export class NgxStructuralsModule { 22 | } 23 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-subscribe/ngx-subscribe.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { of, Subject } from 'rxjs'; 4 | import { NgxSubscribeDirective } from './ngx-subscribe.directive'; 5 | 6 | function setupComponent(component: new(...args: any[]) => T, targetSelector: string): [ComponentFixture, () => HTMLElement] { 7 | TestBed.configureTestingModule({ 8 | declarations: [NgxSubscribeDirective, component], 9 | }); 10 | 11 | TestBed.compileComponents().then(); 12 | const fixture = TestBed.createComponent(component); 13 | fixture.detectChanges(); 14 | 15 | return [ 16 | fixture, 17 | () => (fixture.nativeElement as HTMLElement).querySelector(targetSelector) as HTMLElement, 18 | ]; 19 | } 20 | 21 | describe('ngxSubscribe', () => { 22 | it('subscribes to the observable and shows the emitted value', async(() => { 23 | @Component({ 24 | template: `
{{ value }}
`, 25 | }) 26 | class TestComponent { 27 | public source$ = of(42); 28 | } 29 | 30 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 31 | expect(getTarget().textContent).toBe('42'); 32 | })); 33 | 34 | it('updates as more values emit', async(() => { 35 | @Component({ 36 | template: `
{{ value }}
`, 37 | }) 38 | class TestComponent { 39 | public source$ = new Subject(); 40 | } 41 | 42 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 43 | expect(getTarget().textContent).toBe(''); 44 | 45 | [42, 1337].forEach(i => { 46 | fixture.componentInstance.source$.next(i); 47 | fixture.detectChanges(); 48 | expect(getTarget().textContent).toBe(`${i}`); 49 | }); 50 | })); 51 | 52 | it('counts the number of emitted values', async(async () => { 53 | @Component({ 54 | template: `
{{ count }}
`, 55 | }) 56 | class TestComponent { 57 | public source$ = new Subject(); 58 | } 59 | 60 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 61 | expect(getTarget().textContent).toBe('0'); 62 | 63 | [1, 2, 3].forEach(i => { 64 | fixture.componentInstance.source$.next(); 65 | fixture.detectChanges(); 66 | expect(getTarget().textContent).toBe(`${i}`); 67 | }); 68 | })); 69 | 70 | it('contains information about the error of the observable', async(async () => { 71 | @Component({ 72 | template: ` 73 |
74 | {{ errored }} | {{ error }} 75 |
76 | `, 77 | }) 78 | class TestComponent { 79 | public source$ = new Subject(); 80 | } 81 | 82 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 83 | expect(getTarget().textContent.trim()).toBe('false |'); 84 | 85 | fixture.componentInstance.source$.error(42); 86 | fixture.detectChanges(); 87 | expect(getTarget().textContent.trim()).toBe('true | 42'); 88 | })); 89 | 90 | it('contains information about the completion of the observable', async(async () => { 91 | @Component({ 92 | template: ` 93 |
94 | {{ completed }} 95 |
96 | `, 97 | }) 98 | class TestComponent { 99 | public source$ = new Subject(); 100 | } 101 | 102 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 103 | expect(getTarget().textContent.trim()).toBe('false'); 104 | 105 | fixture.componentInstance.source$.complete(); 106 | fixture.detectChanges(); 107 | expect(getTarget().textContent.trim()).toBe('true'); 108 | })); 109 | 110 | it('can display a different template', async(async () => { 111 | @Component({ 112 | template: ` 113 |
114 | Pending 115 | {{ value }} 116 |
117 | `, 118 | }) 119 | class TestComponent { 120 | public source$ = new Subject(); 121 | } 122 | 123 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 124 | expect(getTarget().textContent).toBe('Pending'); 125 | 126 | fixture.componentInstance.source$.next(42); 127 | fixture.detectChanges(); 128 | expect(getTarget().textContent).toBe('42'); 129 | })); 130 | 131 | it('can display a different template if no value has been emitted yet', async(async () => { 132 | @Component({ 133 | template: ` 134 |
135 | 136 | Pending 137 |
138 | `, 139 | }) 140 | class TestComponent { 141 | public source$ = new Subject(); 142 | } 143 | 144 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 145 | expect(getTarget().textContent).toBe('Pending'); 146 | 147 | fixture.componentInstance.source$.next(); 148 | fixture.detectChanges(); 149 | expect(getTarget().textContent).toBe(''); 150 | })); 151 | 152 | it('can display a different template if an error occurred', async(async () => { 153 | @Component({ 154 | template: ` 155 |
156 | 157 | Error 158 |
159 | `, 160 | }) 161 | class TestComponent { 162 | public source$ = new Subject(); 163 | } 164 | 165 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 166 | expect(getTarget().textContent).toBe(''); 167 | 168 | fixture.componentInstance.source$.error(42); 169 | fixture.detectChanges(); 170 | expect(getTarget().textContent).toBe('Error'); 171 | })); 172 | 173 | it('can display a different template if the observable completed', async(async () => { 174 | @Component({ 175 | template: ` 176 |
177 | 178 | Completed 179 |
180 | `, 181 | }) 182 | class TestComponent { 183 | public source$ = new Subject(); 184 | } 185 | 186 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 187 | expect(getTarget().textContent).toBe(''); 188 | 189 | fixture.componentInstance.source$.complete(); 190 | fixture.detectChanges(); 191 | expect(getTarget().textContent).toBe('Completed'); 192 | })); 193 | 194 | it('resubscribes if the observable changes', async(async () => { 195 | @Component({ 196 | template: ` 197 |
198 | {{ value }} | {{ count }} | {{ errored }} | {{ error }} 199 |
200 | `, 201 | }) 202 | class TestComponent { 203 | public source$ = new Subject(); 204 | } 205 | 206 | const [fixture, getTarget] = setupComponent(TestComponent, 'div'); 207 | fixture.componentInstance.source$.next(42); 208 | fixture.componentInstance.source$.error(1337); 209 | fixture.detectChanges(); 210 | expect(getTarget().textContent.trim()).toBe('42 | 1 | true | 1337'); 211 | 212 | fixture.componentInstance.source$ = new Subject(); 213 | fixture.detectChanges(); 214 | expect(getTarget().textContent.trim()).toBe('| 0 | false |'); 215 | })); 216 | }); 217 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-subscribe/ngx-subscribe.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, EmbeddedViewRef, Input, OnDestroy, Optional, TemplateRef, ViewContainerRef } from '@angular/core'; 2 | import { Observable, Subscription } from 'rxjs'; 3 | 4 | /** 5 | * Context provided when using the [ngxSubscribe] directive. 6 | * 7 | * @publicApi 8 | */ 9 | export class NgxSubscribeContext { 10 | /** 11 | * The (most recently) emitted value of the bound observable. 12 | * 13 | * This always keeps the last emitted value of the bound observable and is {@code null} if the observable has not yet emitted. 14 | * To distinguish an emitted {@code null} value from this, use the {@link count} context variable. 15 | * 16 | * @publicApi 17 | */ 18 | public $implicit: T | null = null; 19 | /** 20 | * Whether the bound observable has errored. 21 | * 22 | * This is {@code true} if and only if the currently bound observable has errored. You can access the error using {@link error}. 23 | * 24 | * @publicApi 25 | */ 26 | public errored = false; 27 | /** 28 | * Whether the bound observable has completed. 29 | * 30 | * This is {@code true} if and only if the currently bound observable has completed. 31 | * 32 | * @publicApi 33 | */ 34 | public completed = false; 35 | /** 36 | * Error thrown by the observable. 37 | * 38 | * Holds the error thrown by the observable if it has indeed errored. This can be checked using the {@link errored} context member. 39 | * Otherwise this holds the value {@code null}. 40 | * 41 | * @publicApi 42 | */ 43 | public error: any | null = null; 44 | /** 45 | * Number of emitted values. 46 | * 47 | * This counter increases any time the currently bound observable emits a value. 48 | * 49 | * @publicApi 50 | */ 51 | public count = 0; 52 | } 53 | 54 | /** 55 | * Subscribes to a given observable and renders a template. 56 | * 57 | * You can use the [ngxSubscribe] structural directive to subscribe to an observable directly from the template of your Angular component. 58 | * Using the provided context information you have access to all important information. You can also define different templates to be 59 | * rendered depending on whether the observable has (not yet) emitted, errored or completed. 60 | * 61 | * @publicApi 62 | */ 63 | @Directive({ 64 | selector: '[ngxSubscribe]', 65 | }) 66 | export class NgxSubscribeDirective implements OnDestroy { 67 | 68 | private context = new NgxSubscribeContext(); 69 | 70 | private thenTemplateRef: TemplateRef> | null = null; 71 | private thenViewRef: EmbeddedViewRef> | null = null; 72 | private beforeAnyTemplate: TemplateRef> | null = null; 73 | private beforeAnyViewRef: EmbeddedViewRef> | null = null; 74 | private onErrorTemplateRef: TemplateRef> | null = null; 75 | private onErrorViewRef: EmbeddedViewRef> | null = null; 76 | private onCompletedTemplateRef: TemplateRef> | null = null; 77 | private onCompletedViewRef: EmbeddedViewRef> | null = null; 78 | 79 | private source$: Observable | null = null; 80 | private subscription: Subscription | null = null; 81 | 82 | constructor( 83 | private readonly viewContainer: ViewContainerRef, 84 | @Optional() templateRef: TemplateRef>, 85 | ) { 86 | if (!templateRef) { 87 | throw new Error(`[ngxSubscribe] can only be used as a structural directive or on an ng-template.`); 88 | } 89 | 90 | this.thenTemplateRef = templateRef; 91 | this.beforeAnyTemplate = templateRef; 92 | this.onErrorTemplateRef = templateRef; 93 | this.onCompletedTemplateRef = templateRef; 94 | } 95 | 96 | /** @internal */ 97 | public static ngTemplateContextGuard(dir: NgxSubscribeDirective, ctx: unknown): ctx is NgxSubscribeContext { 98 | return true; 99 | } 100 | 101 | /** 102 | * See {@link NgxSubscribeDirective#ngxSubscribeOf}. 103 | * 104 | * @publicApi 105 | */ 106 | @Input() 107 | public set ngxSubscribe(source$: Observable | string) { 108 | if (typeof source$ === 'string') { 109 | // When using *ngxSubscribe="let value of source$" the compile desuggars this to include ngxSubscribe="". 110 | // With Ivy's stricter template type-checking this will cause a typing error. Therefore, we do allow 111 | // this input to be a string to make the type-checker happy, and simply do nothing if a string was 112 | // passed as the ngxSubscribeOf input will be used instead. 113 | return; 114 | } 115 | 116 | this.ngxSubscribeOf = source$; 117 | } 118 | 119 | /** 120 | * Observable to subscribe to. 121 | * 122 | * This is the primary input for the {@code [ngxSubscribe]} directive and defines the observable which should be subscribed to. 123 | * 124 | * @publicApi 125 | */ 126 | @Input() 127 | public set ngxSubscribeOf(source$: Observable) { 128 | if (this.source$ === source$) { 129 | return; 130 | } 131 | 132 | if (this.subscription) { 133 | this.subscription.unsubscribe(); 134 | this.subscription = null; 135 | 136 | this.context = new NgxSubscribeContext(); 137 | this.clearViewRefs(); 138 | } 139 | 140 | if (source$) { 141 | this.subscription = source$.subscribe({ 142 | next: value => { 143 | this.context.$implicit = value; 144 | this.context.count++; 145 | this.updateView(); 146 | }, 147 | error: err => { 148 | this.context.errored = true; 149 | this.context.error = err; 150 | this.updateView(); 151 | }, 152 | complete: () => { 153 | this.context.completed = true; 154 | this.updateView(); 155 | }, 156 | }); 157 | } 158 | 159 | this.updateView(); 160 | } 161 | 162 | /** 163 | * Template to show for emitted values. 164 | * 165 | * Defines the template to be shown when the observable emits a value. 166 | * 167 | * @publicApi 168 | */ 169 | @Input() 170 | public set ngxSubscribeThen(templateRef: TemplateRef> | null) { 171 | this.thenTemplateRef = templateRef; 172 | this.thenViewRef = null; 173 | this.updateView(); 174 | } 175 | 176 | /** 177 | * Template to show while observable has not emitted, errored or completed. 178 | * 179 | * Defines the template to be shown before the observable has either emitted a value, errored or completed. 180 | * If not specified, this defaults to the template on which the directive has been applied. 181 | * 182 | * @publicApi 183 | */ 184 | @Input() 185 | public set ngxSubscribeBeforeAny(templateRef: TemplateRef> | null) { 186 | this.beforeAnyTemplate = templateRef; 187 | this.beforeAnyViewRef = null; 188 | this.updateView(); 189 | } 190 | 191 | /** 192 | * Template to show if the observable errored. 193 | * 194 | * Defines the template to be shown in case the observable has errored. 195 | * If not specified, this defaults to the template on which the directive has been applied. 196 | * 197 | * @publicApi 198 | */ 199 | @Input() 200 | public set ngxSubscribeOnError(templateRef: TemplateRef> | null) { 201 | this.onErrorTemplateRef = templateRef; 202 | this.onErrorViewRef = null; 203 | this.updateView(); 204 | } 205 | 206 | /** 207 | * Template to show if the observable completed. 208 | * 209 | * Defines the template to be shown in case the observable completed. 210 | * If not specified, this defaults to the template on which the directive has been applied. 211 | * 212 | * @publicApi 213 | */ 214 | @Input() 215 | public set ngxSubscribeOnCompleted(templateRef: TemplateRef> | null) { 216 | this.onCompletedTemplateRef = templateRef; 217 | this.onCompletedViewRef = null; 218 | this.updateView(); 219 | } 220 | 221 | /** @internal */ 222 | public ngOnDestroy(): void { 223 | if (this.subscription) { 224 | this.subscription.unsubscribe(); 225 | } 226 | } 227 | 228 | private updateView(): void { 229 | if (this.context.completed) { 230 | if (!this.onCompletedViewRef) { 231 | this.clearViewRefs(); 232 | if (this.onCompletedTemplateRef) { 233 | this.onCompletedViewRef = this.viewContainer.createEmbeddedView(this.onCompletedTemplateRef, this.context); 234 | } 235 | } 236 | } else if (this.context.errored) { 237 | if (!this.onErrorViewRef) { 238 | this.clearViewRefs(); 239 | if (this.onErrorTemplateRef) { 240 | this.onErrorViewRef = this.viewContainer.createEmbeddedView(this.onErrorTemplateRef, this.context); 241 | } 242 | } 243 | } else if (this.context.count === 0) { 244 | if (!this.thenViewRef) { 245 | this.clearViewRefs(); 246 | if (this.beforeAnyTemplate) { 247 | this.beforeAnyViewRef = this.viewContainer.createEmbeddedView(this.beforeAnyTemplate, this.context); 248 | } 249 | } 250 | } else { 251 | if (!this.thenViewRef) { 252 | this.clearViewRefs(); 253 | if (this.thenTemplateRef) { 254 | this.thenViewRef = this.viewContainer.createEmbeddedView(this.thenTemplateRef, this.context); 255 | } 256 | } 257 | } 258 | } 259 | 260 | private clearViewRefs(): void { 261 | this.viewContainer.clear(); 262 | 263 | this.thenViewRef = null; 264 | this.onErrorViewRef = null; 265 | this.onCompletedViewRef = null; 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-subscribe/ngx-subscribe.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgxSubscribeDirective } from './ngx-subscribe.directive'; 4 | 5 | @NgModule({ 6 | declarations: [NgxSubscribeDirective], 7 | exports: [NgxSubscribeDirective], 8 | imports: [ 9 | CommonModule, 10 | ] 11 | }) 12 | export class NgxSubscribeModule { 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-subscribe/public-api.ts: -------------------------------------------------------------------------------- 1 | export { NgxSubscribeModule } from './ngx-subscribe.module'; 2 | export { NgxSubscribeDirective } from './ngx-subscribe.directive'; -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-template-context/ngx-template-context.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { NgxTemplateContextDirective } from './ngx-template-context.directive'; 3 | import { Component } from '@angular/core'; 4 | 5 | function setupComponent(component: new(...args: any[]) => T, targetSelector?: string): [ComponentFixture, () => HTMLElement] { 6 | TestBed.configureTestingModule({ 7 | declarations: [NgxTemplateContextDirective, component], 8 | }); 9 | 10 | TestBed.compileComponents().then(); 11 | const fixture = TestBed.createComponent(component); 12 | fixture.detectChanges(); 13 | 14 | return [ 15 | fixture, 16 | () => targetSelector 17 | ? (fixture.nativeElement as HTMLElement).querySelector(targetSelector) as HTMLElement 18 | : fixture.nativeElement as HTMLElement, 19 | ]; 20 | } 21 | 22 | describe('ngxTemplateContext', () => { 23 | it('should render the template as usual with the additional directive', () => { 24 | @Component({ 25 | template: ` 26 | {{ data.prop }} 27 | 28 | `, 29 | }) 30 | class TestComponent { 31 | public context: { $implicit: { prop: number } } = {$implicit: {prop: 42}}; 32 | } 33 | 34 | const [_, getTarget] = setupComponent(TestComponent); 35 | expect(getTarget().textContent.trim()).toBe('42'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-template-context/ngx-template-context.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input } from '@angular/core'; 2 | 3 | /** 4 | * Defines the template context 5 | * 6 | * You can use [ngxTemplateContext] to specify the the template context inside a ng-template tag body 7 | * by providing a variable with the desired type. 8 | * 9 | * @publicApi 10 | */ 11 | @Directive({ 12 | selector: '[ngxTemplateContext]' 13 | }) 14 | export class NgxTemplateContextDirective { 15 | /** 16 | * Variable defining the Template Context 17 | * 18 | * @publicApi 19 | */ 20 | @Input() 21 | ngxTemplateContext?: T; 22 | 23 | /** @internal */ 24 | static ngTemplateContextGuard( 25 | dir: NgxTemplateContextDirective, 26 | ctx: unknown 27 | ): ctx is T { 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-template-context/ngx-template-context.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgxTemplateContextDirective } from './ngx-template-context.directive'; 4 | 5 | @NgModule({ 6 | declarations: [NgxTemplateContextDirective], 7 | exports: [NgxTemplateContextDirective], 8 | imports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class NgxTemplateContextModule { 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/lib/ngx-template-context/public-api.ts: -------------------------------------------------------------------------------- 1 | export { NgxTemplateContextModule } from './ngx-template-context.module'; 2 | export { NgxTemplateContextDirective } from './ngx-template-context.directive'; 3 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/public-api.ts: -------------------------------------------------------------------------------- 1 | export { NgxStructuralsModule } from './lib/ngx-structurals.module'; 2 | 3 | export * from './lib/ngx-subscribe/public-api'; 4 | export * from './lib/ngx-repeat/public-api'; 5 | export * from './lib/ngx-alias/public-api'; 6 | export * from './lib/ngx-template-context/public-api'; 7 | -------------------------------------------------------------------------------- /projects/ngx-structurals/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: any; 12 | 13 | // First, initialize the Angular testing environment. 14 | getTestBed().initTestEnvironment( 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting() 17 | ); 18 | // Then we find all the tests. 19 | const context = require.context('./', true, /\.spec\.ts$/); 20 | // And load the modules. 21 | context.keys().map(context); 22 | -------------------------------------------------------------------------------- /projects/ngx-structurals/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ], 13 | "strict": true 14 | }, 15 | "angularCompilerOptions": { 16 | "skipTemplateCodegen": true, 17 | "strictMetadataEmit": true, 18 | "fullTemplateTypeCheck": true, 19 | "strictInjectionParameters": true, 20 | "enableResourceInlining": true, 21 | "strictTemplateTypeCheck": true 22 | }, 23 | "exclude": [ 24 | "src/test.ts", 25 | "**/*.spec.ts" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /projects/ngx-structurals/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "enableIvy": false 5 | } 6 | } -------------------------------------------------------------------------------- /projects/ngx-structurals/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/ngx-structurals/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "ngx", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "ngx", 14 | "kebab-case" 15 | ], 16 | "eofline": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | rm -rf dist/ngx-structurals 6 | lerna version 7 | ng build --prod ngx-structurals 8 | 9 | cp README.md dist/ngx-structurals 10 | cp LICENSE dist/ngx-structurals 11 | 12 | cd dist/ngx-structurals && npm publish --access public --registry https://registry.npmjs.org/ ; cd - 13 | 14 | git push 15 | git push --tags 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ngx-structurals": [ 23 | "dist/ngx-structurals" 24 | ], 25 | "ngx-structurals/*": [ 26 | "dist/ngx-structurals/*" 27 | ] 28 | } 29 | }, 30 | "angularCompilerOptions": { 31 | "fullTemplateTypeCheck": true, 32 | "strictInjectionParameters": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-parens": false, 15 | "arrow-return-shorthand": true, 16 | "deprecation": { 17 | "severity": "warning" 18 | }, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "curly": true, 24 | "interface-name": false, 25 | "max-classes-per-file": false, 26 | "eofline": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "import-spacing": true, 32 | "indent": { 33 | "options": [ 34 | "spaces" 35 | ] 36 | }, 37 | "member-access": false, 38 | "member-ordering": [ 39 | true, 40 | { 41 | "order": [ 42 | "static-field", 43 | "instance-field", 44 | "static-method", 45 | "instance-method" 46 | ] 47 | } 48 | ], 49 | "no-consecutive-blank-lines": false, 50 | "no-console": [ 51 | true, 52 | "debug", 53 | "info", 54 | "time", 55 | "timeEnd", 56 | "trace" 57 | ], 58 | "no-empty": false, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-non-null-assertion": true, 64 | "no-redundant-jsdoc": true, 65 | "no-switch-case-fall-through": true, 66 | "no-var-requires": false, 67 | "object-literal-key-quotes": [ 68 | true, 69 | "as-needed" 70 | ], 71 | "object-literal-sort-keys": false, 72 | "ordered-imports": false, 73 | "quotemark": [ 74 | true, 75 | "single" 76 | ], 77 | "trailing-comma": false, 78 | "component-class-suffix": true, 79 | "contextual-lifecycle": true, 80 | "directive-class-suffix": true, 81 | "no-conflicting-lifecycle": true, 82 | "no-host-metadata-property": true, 83 | "no-input-rename": true, 84 | "no-inputs-metadata-property": true, 85 | "no-output-native": true, 86 | "no-output-on-prefix": true, 87 | "no-output-rename": true, 88 | "semicolon": { 89 | "options": [ 90 | "always" 91 | ] 92 | }, 93 | "space-before-function-paren": { 94 | "options": { 95 | "anonymous": "never", 96 | "asyncArrow": "always", 97 | "constructor": "never", 98 | "method": "never", 99 | "named": "never" 100 | } 101 | }, 102 | "no-outputs-metadata-property": true, 103 | "template-banana-in-box": true, 104 | "template-no-negated-async": true, 105 | "typedef-whitespace": { 106 | "options": [ 107 | { 108 | "call-signature": "nospace", 109 | "index-signature": "nospace", 110 | "parameter": "nospace", 111 | "property-declaration": "nospace", 112 | "variable-declaration": "nospace" 113 | }, 114 | { 115 | "call-signature": "onespace", 116 | "index-signature": "onespace", 117 | "parameter": "onespace", 118 | "property-declaration": "onespace", 119 | "variable-declaration": "onespace" 120 | } 121 | ] 122 | }, 123 | "use-lifecycle-interface": true, 124 | "use-pipe-transform-interface": true, 125 | "variable-name": { 126 | "options": [ 127 | "ban-keywords", 128 | "check-format", 129 | "allow-pascal-case" 130 | ] 131 | }, 132 | "whitespace": { 133 | "options": [ 134 | "check-branch", 135 | "check-decl", 136 | "check-operator", 137 | "check-separator", 138 | "check-type", 139 | "check-typecast" 140 | ] 141 | } 142 | } 143 | } 144 | --------------------------------------------------------------------------------