├── .circleci
└── config.yml
├── .editorconfig
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .lintstagedrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── commitlint.config.js
├── lerna.json
├── package.json
├── packages
└── @ngx-meta
│ └── core
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── index.ts
│ ├── meta.guard.ts
│ ├── meta.loader.ts
│ ├── meta.module.ts
│ ├── meta.service.ts
│ ├── models
│ │ ├── meta-settings.ts
│ │ └── page-title-positioning.ts
│ └── util.ts
│ └── tests
│ ├── common.ts
│ ├── meta.loader.spec.ts
│ ├── meta.service.spec.ts
│ └── util.spec.ts
├── tools
├── build
│ ├── helpers.ts
│ ├── packager.ts
│ └── tsconfig.package.json
├── test
│ └── jest.setup.ts
└── tslint.json
├── tsconfig.json
├── tsconfig.lint.json
├── tslint.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/node:10-browsers
6 | environment:
7 | JOBS: 1
8 | steps:
9 | - checkout
10 | - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
11 | - run: git config --global user.email ${GH_USER_EMAIL}
12 | - run: git config --global user.name ${GH_USER_NAME}
13 | - run: sudo yarn global add greenkeeper-lockfile@1
14 | - run: sudo yarn global add lerna@3
15 | - restore_cache:
16 | keys:
17 | - deps-{{ .Branch }}-{{ checksum "yarn.lock" }}
18 | - deps-
19 | - run: yarn
20 | - save_cache:
21 | key: deps-{{ .Branch }}-{{ checksum "yarn.lock" }}
22 | paths: 'node_modules'
23 | - run: yarn ci:before
24 | - run: yarn test:ci
25 | - run: if [ ${CIRCLE_BRANCH} == "master" ]; then lerna version --create-release github --yes; fi
26 | - run: yarn build
27 | - run: yarn ci:after
28 | - run: if [ ${CIRCLE_BRANCH} == "master" ]; then lerna publish from-package --yes; fi
29 | - run: bash <(curl -s https://codecov.io/bash)
30 | - store_artifacts:
31 | path: coverage
32 | prefix: coverage
33 | - store_artifacts:
34 | path: dist
35 | prefix: dist
36 | - store_test_results:
37 | path: test-report.xml
38 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 140
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # More about this https://help.github.com/articles/about-codeowners/
2 | * @fulls1z3
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | **I'm submitting a ...** (check one with "x")
8 | ```
9 | [ ] Regression (a behavior that used to work and stopped working in a new release)
10 | [ ] Bug report
11 | [ ] Support request =>
12 | [ ] Feature request
13 | [ ] Documentation issue or request
14 | ```
15 |
16 | **Current behavior**
17 |
18 |
19 | **Expected/desired behavior**
20 |
21 |
22 | **Minimal reproduction of the problem with instructions**
23 |
27 |
28 | **What is the motivation / use case for changing the behavior?**
29 |
30 |
31 | **Environment**
32 | * **Angular version:** X.Y.Z
33 |
34 |
35 | * **Browser:**
36 | - [ ] Chrome (desktop) version XX
37 | - [ ] Chrome (Android) version XX
38 | - [ ] Chrome (iOS) version XX
39 | - [ ] Firefox version XX
40 | - [ ] Safari (desktop) version XX
41 | - [ ] Safari (iOS) version XX
42 | - [ ] IE version XX
43 | - [ ] Edge version XX
44 |
45 | * **For Tooling issues:**
46 | - Node version: XX
47 | - Platform:
48 |
49 | * Others:
50 |
51 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ** PR Checklist
2 | Please check if your PR fulfills the following requirements:
3 |
4 | - [ ] The commit message follows our guidelines: https://github.com/fulls1z3/ngx-meta/blob/master/CONTRIBUTING.md#commit
5 | - [ ] Tests for the changes have been added (for bug fixes / features)
6 | - [ ] Docs have been added / updated (for bug fixes / features)
7 |
8 | ** PR Type
9 | What kind of change does this PR introduce?
10 |
11 |
12 | ```
13 | [ ] Bugfix
14 | [ ] Feature
15 | [ ] Code style update (formatting, local variables)
16 | [ ] Refactoring (no functional changes, no api changes)
17 | [ ] Build related changes
18 | [ ] CI related changes
19 | [ ] Documentation content changes
20 | [ ] Other... Please describe:
21 | ```
22 |
23 | ** What is the current behavior?
24 |
25 |
26 | Issue Number: N/A
27 |
28 | ** What is the new behavior?
29 |
30 | ** Does this PR introduce a breaking change?
31 | ```
32 | [ ] Yes
33 | [ ] No
34 | ```
35 |
36 |
37 |
38 | ** Other information
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | test-report.xml
36 | /typings
37 |
38 | # System Files
39 | .DS_Store
40 | Thumbs.db
41 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*.{json,css,scss,md,js}": ["prettier --write", "git add"],
3 | "*.(ts)": ["prettier-tslint fix", "git add"]
4 | }
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | # compiled output
4 | /dist
5 | /docs
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | test-report.xml
37 | /typings
38 |
39 | # System Files
40 | .DS_Store
41 | Thumbs.db
42 |
43 | # others
44 | /tools
45 | angular.json
46 | yarn.lock
47 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 140,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/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 | # [9.0.0](https://github.com/fulls1z3/ngx-meta/compare/v8.1.0...v9.0.0) (2020-04-27)
7 |
8 |
9 | ### Features
10 |
11 | * **package:** migrate to ng9 ([bcc28c9](https://github.com/fulls1z3/ngx-meta/commit/bcc28c97dcff86fb90ee3f7f985ab3fc0c5456d5))
12 | * **package:** migrate to ng9 ([e9b933a](https://github.com/fulls1z3/ngx-meta/commit/e9b933ae7447860329ef15014bcfb37b8d0620a8))
13 |
14 |
15 | ### BREAKING CHANGES
16 |
17 | * **package:** ng9
18 | * **package:** ng9
19 |
20 |
21 |
22 |
23 |
24 | # [8.1.0](https://github.com/fulls1z3/ngx-meta/compare/v8.0.2...v8.1.0) (2020-04-27)
25 |
26 |
27 | ### Features
28 |
29 | * **package:** migrate to ng9 ([#207](https://github.com/fulls1z3/ngx-meta/issues/207)) ([38325c5](https://github.com/fulls1z3/ngx-meta/commit/38325c5a65941eb94dbfb67ad6e7cdf8ca37630f))
30 |
31 |
32 |
33 |
34 |
35 | ## [8.0.2](https://github.com/fulls1z3/ngx-meta/compare/v8.0.1...v8.0.2) (2019-12-02)
36 |
37 |
38 | ### Bug Fixes
39 |
40 | * **packaging:** fix deployment ([#202](https://github.com/fulls1z3/ngx-meta/issues/202)) ([cfa3f2d](https://github.com/fulls1z3/ngx-meta/commit/cfa3f2dbf856555f00229aa2a8a9a24a169925e2))
41 | * **packaging:** fix deployment ([#203](https://github.com/fulls1z3/ngx-meta/issues/203)) ([aa07870](https://github.com/fulls1z3/ngx-meta/commit/aa07870278be72fdfa7a2c4aa451c67223735f18))
42 |
43 |
44 |
45 |
46 |
47 | ## [8.0.1](https://github.com/fulls1z3/ngx-meta/compare/v8.0.0...v8.0.1) (2019-11-21)
48 |
49 | **Note:** Version bump only for package ngx-meta
50 |
51 | # [8.0.0](https://github.com/fulls1z3/ngx-meta/compare/v7.0.10...v8.0.0) (2019-11-21)
52 |
53 | ### Features
54 |
55 | - **package:** upgrade to angular 8 ([a741f1f](https://github.com/fulls1z3/ngx-meta/commit/a741f1f83684237a6b2fad596f7fe7a56c02182c))
56 |
57 | ### BREAKING CHANGES
58 |
59 | - **package:** ng8
60 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mail@buraktasci.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to ngx-meta
2 |
3 | We would love for you to contribute to **`ngx-meta`** and help make it even better than it is today! As a contributor,
4 | here are the guidelines we would like you to follow:
5 |
6 | - [Code of Conduct](#coc)
7 | - [Issues and Bugs](#issue)
8 | - [Feature requests](#feature)
9 | - [Submission guidelines](#submit)
10 | - [Coding rules](#rules)
11 | - [Commit message guidelines](#commit)
12 |
13 | ## Code of Conduct
14 | Help us keep **`ngx-meta`** open and inclusive. Please read and follow our [Code of Conduct][coc].
15 |
16 | ## Found a Bug?
17 | If you find a bug in the source code, you can help us by [submitting an issue](#submit-issue) to our [GitHub Repository][github].
18 |
19 | Even better, you can [submit a Pull Request](#submit-pr) with a fix.
20 |
21 | ## Missing a Feature?
22 | You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub Repository.
23 |
24 | If you would like to *implement* a new feature, please submit an issue with a proposal for your work first, to be sure that
25 | we can use it.
26 |
27 | Please consider what kind of change it is:
28 | * For a **Major Feature**, first open an issue and outline your proposal so that it can be discussed.
29 | This will also allow us to better coordinate our efforts, prevent duplication of work, and help you to craft the change
30 | so that it is successfully accepted into the project.
31 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
32 |
33 | ## Submission guidelines
34 | ### Submitting an Issue
35 | Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion
36 | might inform you of workarounds readily available.
37 |
38 | We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order
39 | to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co.
40 |
41 | Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional
42 | questions like:
43 | - version used
44 | - 3rd-party libraries and their versions
45 | - and most importantly: a use-case that fails
46 |
47 | A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as
48 | well as confirm that we are fixing the right problem. If plunker is not a suitable way to demonstrate the problem (*ex:
49 | issues related to our npm packaging*), please create a standalone git repository demonstrating the problem.
50 |
51 | We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more
52 | bugs.
53 |
54 | Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand
55 | that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate
56 | the problem before we can fix it.
57 |
58 | Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you,
59 | we are going to close an issue that don't have enough info to be reproduced.
60 |
61 | You can file new issues by filling out our [new issue form](https://github.com/fulls1z3/ngx-meta/issues/new).
62 |
63 | ### Submitting a Pull Request (PR)
64 | Before you submit your Pull Request (PR) consider the following guidelines:
65 |
66 | * Search [GitHub](https://github.com/fulls1z3/ngx-meta/pulls) for an open or closed PR that relates to your submission.
67 | You don't want to duplicate effort.
68 | * Make your changes in a new git branch:
69 | ```shell
70 | git checkout -b my-fix-branch master
71 | ```
72 | * Create your patch, **including appropriate test cases**.
73 | * Follow our [Coding rules](#rules).
74 | * Run the full test suite and ensure that all tests pass.
75 | * Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit).
76 | Adherence to these conventions is necessary because release notes are automatically generated from these messages.
77 | ```shell
78 | git commit -a
79 | ```
80 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
81 | * Push your branch to GitHub:
82 | ```shell
83 | git push origin my-fix-branch
84 | ```
85 | * In GitHub, send a pull request to `ngx-meta:master`.
86 | * If we suggest changes then:
87 | * Make the required updates.
88 | * Re-run the test suites to ensure tests are still passing.
89 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
90 | ```shell
91 | git rebase master -i
92 | git push -f
93 | ```
94 | That's it, thanks for your contribution!
95 |
96 | #### After your pull request is merged
97 | After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:
98 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
99 | ```shell
100 | git push origin --delete my-fix-branch
101 | ```
102 | * Check out the master branch:
103 | ```shell
104 | git checkout master -f
105 | ```
106 | * Delete the local branch:
107 | ```shell
108 | git branch -D my-fix-branch
109 | ```
110 | * Update your master with the latest upstream version:
111 | ```shell
112 | git pull --ff upstream master
113 | ```
114 |
115 | ## Coding rules
116 | To ensure consistency throughout the source code, keep these rules in mind as you are working:
117 | * All features or bug fixes **must be tested** by one or more specs (unit-tests).
118 | * All public API methods **must be documented**. (Details TBC).
119 | * We follow [fulls1z3's Angular TSLint rules][angular-tslint-rules].
120 |
121 | ## Commit message guidelines
122 | We have very precise rules over how our git commit messages can be formatted. This leads to **more readable messages** that
123 | are easy to follow when looking through the **project history**. But also, we use the git commit messages to **generate
124 | the `ngx-meta` change log**.
125 |
126 | ### Commit Message Format
127 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special format that includes
128 | a **type**, a **scope** (*when applicable*) and a **subject**:
129 | ```
130 | ():
131 |
132 |
133 |
134 |
135 | ```
136 |
137 | The **header** is mandatory and the **scope** of the header is optional.
138 |
139 | Any line of the commit message cannot be longer 100 characters. This allows the message to be easier to read on GitHub as
140 | well as in various git tools.
141 |
142 | Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/)
143 | if any.
144 |
145 | Samples: (even more [samples](https://github.com/fulls1z3/ngx-meta/commits/master))
146 | ```
147 | docs(changelog): update change log to alpha.4
148 | ```
149 | ```
150 | fix(release): need to depend on latest rxjs and zone.js
151 |
152 | The version in our package.json gets copied to the one we publish, and users need the latest of these.
153 | ```
154 |
155 | ### Revert
156 | If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit.
157 | In the body it should say: `This reverts commit .`, where the hash is the SHA of the commit being reverted.
158 |
159 | ### Type
160 | Must be one of the following:
161 | * **build**: Changes that affect the build system or external dependencies (example scopes: gulp, npm, webpack)
162 | * **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, etc)
163 | * **docs**: Documentation only changes
164 | * **feat**: A new feature
165 | * **fix**: A bug fix
166 | * **perf**: A code change that improves performance
167 | * **refactor**: A code change that neither fixes a bug nor adds a feature
168 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
169 | * **test**: Adding missing tests or correcting existing tests
170 |
171 | ### Scope
172 | The scope should be the name of the project affected.
173 |
174 | The following is the list of supported scopes:
175 | * **core**
176 |
177 | There are currently a few exceptions to the "use project name" rule:
178 |
179 | * **packaging**: used for changes that change the package layout (*e.g. package.json, bundles, path changes, etc.*)
180 | * **changelog**: used for updating the release notes in CHANGELOG.md
181 |
182 | ### Subject
183 | The subject contains succinct description of the change:
184 | * use the imperative, present tense: "change" not "changed" nor "changes"
185 | * don't capitalize first letter
186 | * no dot (.) at the end
187 |
188 | ### Body
189 | Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include
190 | the motivation for the change and contrast this with previous behavior.
191 |
192 | ### Footer
193 | The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that
194 | this commit **Closes**.
195 |
196 | **Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit
197 | message is then used for this.
198 |
199 | [coc]: https://github.com/fulls1z3/ngx-meta/blob/master/CODE_OF_CONDUCT.md
200 | [github]: https://github.com/fulls1z3/ngx-meta
201 | [angular-tslint-rules]: https://github.com/fulls1z3/angular-tslint-rules
202 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Burak Tasci
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ngx-meta
2 |
3 | Dynamic page title & meta tags generator for **Angular**
4 |
5 | [](https://circleci.com/gh/fulls1z3/ngx-meta)
6 | [](https://codecov.io/gh/fulls1z3/ngx-meta)
7 | [](https://github.com/facebook/jest)
8 | [](https://conventionalcommits.org)
9 | [](https://greenkeeper.io/)
10 | [](https://angular.io/styleguide)
11 |
12 | > Please support this project by simply putting a Github star. Share this library with friends on Twitter and everywhere else you can.
13 |
14 | **`ngx-meta`** updates the **page title** and **meta tags** every time the route changes, based on **Angular** app's route
15 | configuration.
16 |
17 | - When the **Angular** app uses **server-side** rendering, the meta tags and page titles generated by **`ngx-meta`** successfully
18 | appear on HTML source, due to its platform-free workflow. This allows the SPA to be **crawled and rendered** by the search
19 | engines, as well as **sharing** the website **link** on social networks (facebook, twitter, etc).
20 | - It also supports resolving values [using a `callback` function](https://github.com/fulls1z3/ngx-meta/tree/master/packages/@ngx-meta/core/README.md#using-a-callback-function)
21 | to use a custom logic on the meta tag contents (_`http-get`, [@ngx-translate/core], etc._).
22 |
23 | ## Packages:
24 |
25 | | Name | Description | NPM |
26 | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
27 | | [@ngx-meta/core](https://github.com/fulls1z3/ngx-meta/tree/master/packages/@ngx-meta/core) | Dynamic page title & meta tags generator for **Angular** | [](https://www.npmjs.com/package/@ngx-meta/core) |
28 |
29 | ### Examples
30 |
31 | - [ng-seed/universal] and [fulls1z3/example-app] are officially maintained projects, showcasing common patterns and best
32 | practices for **`ngx-meta`**.
33 |
34 | ## Contributing
35 |
36 | If you want to file a bug, contribute some code, or improve documentation, please read up on the following contribution guidelines:
37 |
38 | - [Issue guidelines](CONTRIBUTING.md#submit)
39 | - [Contributing guidelines](CONTRIBUTING.md)
40 | - [Coding rules](CONTRIBUTING.md#rules)
41 | - [Change log](/releases)
42 |
43 | #### Thanks to
44 |
45 | - [JetBrains], for their support to this open source project with free [WebStorm] licenses.
46 |
47 | ## License
48 |
49 | The MIT License (MIT)
50 |
51 | Copyright (c) 2019 [Burak Tasci]
52 |
53 | [@ngx-translate/core]: https://github.com/ngx-translate/core
54 | [ng-seed/universal]: https://github.com/ng-seed/universal
55 | [fulls1z3/example-app]: https://github.com/fulls1z3/example-app
56 | [jetbrains]: https://www.jetbrains.com/community/opensource
57 | [webstorm]: https://www.jetbrains.com/webstorm
58 | [burak tasci]: https://github.com/fulls1z3
59 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | rules: {
4 | 'scope-enum': [2, 'always', ['core', 'package', 'npm', 'circle', 'lint', 'packaging', 'changelog']]
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/@ngx-meta/*"
4 | ],
5 | "version": "9.0.0",
6 | "npmClient": "yarn",
7 | "command": {
8 | "publish": {
9 | "allowBranch": "master",
10 | "conventionalCommits": true,
11 | "message": "chore(release): release %s"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-meta",
3 | "version": "0.0.0",
4 | "description": "Dynamic page title & meta tags utility for Angular (w/server-side rendering)",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/fulls1z3/ngx-meta.git"
8 | },
9 | "keywords": [],
10 | "author": {
11 | "name": "Burak Tasci",
12 | "email": "mail@buraktasci.com"
13 | },
14 | "license": "MIT",
15 | "bugs": {
16 | "url": "https://github.com/fulls1z3/ngx-meta/issues"
17 | },
18 | "homepage": "https://github.com/fulls1z3/ngx-meta#readme",
19 | "scripts": {
20 | "clean": "rimraf dist",
21 | "build:core": "ts-node tools/build/packager.ts core",
22 | "build": "npm run build:core",
23 | "lint": "tslint -p ./tsconfig.lint.json --force",
24 | "lint:check": "tslint-config-prettier-check ./tslint.lint.json",
25 | "rebuild": "npm run clean && npm run build",
26 | "ci:before": "greenkeeper-lockfile-update",
27 | "ci:after": "greenkeeper-lockfile-upload",
28 | "test": "jest --runInBand --colors",
29 | "test:ci": "jest --ci --updateSnapshot --colors",
30 | "release": "standard-version"
31 | },
32 | "devDependencies": {
33 | "@angular/common": "^9.1.0",
34 | "@angular/compiler": "^9.1.0",
35 | "@angular/compiler-cli": "^9.1.0",
36 | "@angular/core": "^9.1.0",
37 | "@angular/platform-browser": "^9.1.0",
38 | "@angular/platform-browser-dynamic": "^9.1.0",
39 | "@angular/router": "^9.1.0",
40 | "@commitlint/cli": "^8.3.5",
41 | "@commitlint/config-conventional": "^8.3.4",
42 | "@types/jest": "^23.3.14",
43 | "@types/lodash": "^4.14.150",
44 | "@types/node": "^10.0.0",
45 | "angular-tslint-rules": "^1.20.4",
46 | "codelyzer": "^5.2.2",
47 | "cz-conventional-changelog": "^3.1.0",
48 | "husky": "^4.2.0",
49 | "jest": "^23.6.0",
50 | "jest-junit-reporter": "^1.1.0",
51 | "jest-preset-angular": "8.1.2",
52 | "lerna": "^3.20.2",
53 | "lint-staged": "^10.0.0",
54 | "lodash": "^4.17.15",
55 | "ng-packagr": "^9.0.0",
56 | "prettier": "1.19.1",
57 | "prettier-tslint": "^0.4.2",
58 | "reflect-metadata": "^0.1.13",
59 | "request": "^2.88.2",
60 | "rimraf": "^3.0.2",
61 | "rxjs": "~6.5.4",
62 | "ts-node": "^8.9.0",
63 | "tsickle": "^0.38.0",
64 | "tslint": "^5.20.1",
65 | "tslint-config-prettier": "^1.18.0",
66 | "typescript": "~3.8.3",
67 | "zone.js": "^0.10.2"
68 | },
69 | "husky": {
70 | "hooks": {
71 | "pre-commit": "lint-staged",
72 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
73 | }
74 | },
75 | "jest": {
76 | "preset": "jest-preset-angular",
77 | "setupTestFrameworkScriptFile": "./tools/test/jest.setup.ts",
78 | "testResultsProcessor": "./node_modules/jest-junit-reporter",
79 | "testMatch": [
80 | "**/+(*.)+(spec|test).+(ts|js)?(x)"
81 | ],
82 | "globals": {
83 | "ts-jest": {
84 | "tsConfigFile": "./tsconfig.json"
85 | },
86 | "__TRANSFORM_HTML__": true
87 | },
88 | "moduleNameMapper": {
89 | "^@ngx-meta/core": "/packages/@ngx-meta/core/src/index.ts"
90 | },
91 | "cache": false,
92 | "silent": true,
93 | "collectCoverage": true,
94 | "collectCoverageFrom": [
95 | "packages/@ngx-meta/core/src/**.ts"
96 | ]
97 | },
98 | "config": {
99 | "commitizen": {
100 | "path": "./node_modules/cz-conventional-changelog"
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/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 | # [9.0.0](https://github.com/fulls1z3/ngx-meta/compare/v8.1.0...v9.0.0) (2020-04-27)
7 |
8 |
9 | ### Features
10 |
11 | * **package:** migrate to ng9 ([bcc28c9](https://github.com/fulls1z3/ngx-meta/commit/bcc28c97dcff86fb90ee3f7f985ab3fc0c5456d5))
12 |
13 |
14 | ### BREAKING CHANGES
15 |
16 | * **package:** ng9
17 |
18 |
19 |
20 |
21 |
22 | # [8.1.0](https://github.com/fulls1z3/ngx-meta/compare/v8.0.2...v8.1.0) (2020-04-27)
23 |
24 |
25 | ### Features
26 |
27 | * **package:** migrate to ng9 ([#207](https://github.com/fulls1z3/ngx-meta/issues/207)) ([38325c5](https://github.com/fulls1z3/ngx-meta/commit/38325c5a65941eb94dbfb67ad6e7cdf8ca37630f))
28 |
29 |
30 |
31 |
32 |
33 | ## [8.0.2](https://github.com/fulls1z3/ngx-meta/compare/v8.0.1...v8.0.2) (2019-12-02)
34 |
35 |
36 | ### Bug Fixes
37 |
38 | * **packaging:** fix deployment ([#202](https://github.com/fulls1z3/ngx-meta/issues/202)) ([cfa3f2d](https://github.com/fulls1z3/ngx-meta/commit/cfa3f2dbf856555f00229aa2a8a9a24a169925e2))
39 | * **packaging:** fix deployment ([#203](https://github.com/fulls1z3/ngx-meta/issues/203)) ([aa07870](https://github.com/fulls1z3/ngx-meta/commit/aa07870278be72fdfa7a2c4aa451c67223735f18))
40 |
41 |
42 |
43 |
44 |
45 | ## [8.0.1](https://github.com/fulls1z3/ngx-meta/compare/v8.0.0...v8.0.1) (2019-11-21)
46 |
47 | **Note:** Version bump only for package @ngx-meta/core
48 |
49 | # [8.0.0](https://github.com/fulls1z3/ngx-meta/compare/v7.0.10...v8.0.0) (2019-11-21)
50 |
51 | ### Features
52 |
53 | - **package:** upgrade to angular 8 ([a741f1f](https://github.com/fulls1z3/ngx-meta/commit/a741f1f83684237a6b2fad596f7fe7a56c02182c))
54 |
55 | ### BREAKING CHANGES
56 |
57 | - **package:** ng8
58 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/README.md:
--------------------------------------------------------------------------------
1 | # @ngx-meta/core [](https://www.npmjs.com/package/@ngx-meta/core) [](https://www.npmjs.com/package/@ngx-meta/core)
2 |
3 | Dynamic page title & meta tags generator for **Angular**
4 |
5 | [](https://circleci.com/gh/fulls1z3/ngx-meta)
6 | [](https://codecov.io/gh/fulls1z3/ngx-meta)
7 | [](https://github.com/facebook/jest)
8 | [](https://conventionalcommits.org)
9 | [](https://angular.io/styleguide)
10 |
11 | > Please support this project by simply putting a Github star. Share this library with friends on Twitter and everywhere else you can.
12 |
13 | **`@ngx-meta/core`** updates the **page title** and **meta tags** every time the route changes, based on **Angular** app's
14 | route configuration.
15 |
16 | - When the **Angular** app uses **server-side** rendering, the meta tags and page titles generated by **`@ngx-meta/core`**
17 | successfully appear on HTML source, due to its platform-free workflow. This allows the SPA to be **crawled and rendered**
18 | by the search engines, as well as **sharing** the website **link** on social networks (facebook, twitter, etc).
19 | - It also supports resolving values [using a `callback` function](#using-a-callback-function) to use a custom logic on the
20 | meta tag contents (`http-get`, [@ngx-translate/core]).
21 |
22 | ## Table of contents:
23 |
24 | - [Getting started](#getting-started)
25 | - [Installation](#installation) - [Examples](#examples) - [Recommended packages](#recommended-packages) - [Adding `@ngx-meta/core` to your project (SystemJS)](#adding-systemjs) - [Route configuration](#route-config)
26 | - [app.module configuration](#appmodule-config)
27 | - [Settings](#settings) - [Setting up `MetaModule` to use `MetaStaticLoader`](#setting-up-staticloader) - [Using a `callback` function](#using-a-callback-function)
28 | - [Set meta tags programmatically](#set-meta-tags-programmatically)
29 | - [Credits](#credits)
30 | - [License](#license)
31 |
32 | ## Getting started
33 |
34 | ### Installation
35 |
36 | You can install **`@ngx-meta/core`** using `npm`
37 |
38 | ```
39 | npm install @ngx-meta/core --save
40 | ```
41 |
42 | ### Examples
43 |
44 | - [ng-seed/universal] and [fulls1z3/example-app] are officially maintained projects, showcasing common patterns and best
45 | practices for **`@ngx-meta/core`**.
46 |
47 | ### Recommended packages
48 |
49 | The following package(s) have no dependency for **`@ngx-meta/core`**, however may provide supplementary/shorthand functionality:
50 |
51 | - [@ngx-config/core]: provides meta settings from the application settings loaded during application initialization
52 | - [@ngx-translate/core]: provides internationalization (i18n) features to retrieve the translated meta settings
53 |
54 | ### Adding `@ngx-meta/core` to your project (SystemJS)
55 |
56 | Add `map` for **`@ngx-meta/core`** in your `systemjs.config`
57 |
58 | ```javascript
59 | '@ngx-meta/core': 'node_modules/@ngx-meta/core/bundles/core.umd.min.js'
60 | ```
61 |
62 | ### Route configuration
63 |
64 | Import `MetaGuard` using the mapping `'@ngx-meta/core'` and append `canActivate: [MetaGuard]` or `canActivateChild: [MetaGuard]`
65 | properties to the route definitions at **app.routes** (_considering the app.routes is the route definitions in Angular application_).
66 |
67 | Then, add `meta` settings inside the `data` property of routes.
68 |
69 | **Note:** meta properties such as `title`, `description`, `author` and `publisher` will be duplicated as `og:title`, `og:description`,
70 | `og:author` and `og:publisher`, so there's no need to declare them again in this context.
71 |
72 | #### app.routes.ts
73 |
74 | ```TypeScript
75 | ...
76 | import { MetaGuard } from '@ngx-meta/core';
77 | ...
78 | export const routes: Routes = [
79 | {
80 | path: '',
81 | canActivateChild: [MetaGuard],
82 | children: [
83 | {
84 | path: 'home',
85 | component: HomeComponent,
86 | data: {
87 | meta: {
88 | title: 'Sweet home',
89 | description: 'Home, home sweet home... and what?'
90 | }
91 | }
92 | },
93 | {
94 | path: 'duck',
95 | component: DuckComponent,
96 | data: {
97 | meta: {
98 | title: 'Rubber duckie',
99 | description: 'Have you seen my rubber duckie?'
100 | }
101 | }
102 | },
103 | {
104 | path: 'toothpaste',
105 | component: ToothpasteComponent,
106 | data: {
107 | meta: {
108 | title: 'Toothpaste',
109 | override: true, // prevents appending/prepending the application name to the title attribute
110 | description: 'Eating toothpaste is considered to be too healthy!'
111 | }
112 | }
113 | }
114 | ]
115 | }
116 | ...
117 | ];
118 | ```
119 |
120 | ### app.module configuration
121 |
122 | Import `MetaModule` using the mapping `'@ngx-meta/core'` and append `MetaModule.forRoot({...})` within the imports property
123 | of **app.module** (_considering the app.module is the core module in Angular application_).
124 |
125 | #### app.module.ts
126 |
127 | ```TypeScript
128 | ...
129 | import { MetaModule } from '@ngx-meta/core';
130 | ...
131 |
132 | @NgModule({
133 | declarations: [
134 | AppComponent,
135 | ...
136 | ],
137 | ...
138 | imports: [
139 | ...
140 | RouterModule.forRoot(routes),
141 | MetaModule.forRoot()
142 | ],
143 | ...
144 | bootstrap: [AppComponent]
145 | })
146 | ```
147 |
148 | ## Settings
149 |
150 | You can call the [forRoot] static method using the `MetaStaticLoader`. By default, it is configured to **prepend page titles**
151 | after the **application name** (_if any set_). These **default meta settings** are used when a route doesn't contain any
152 | `meta` settings in its `data` property.
153 |
154 | > You can customize this behavior (_and ofc other settings_) by supplying **meta settings** to `MetaStaticLoader`.
155 |
156 | The following example shows the use of an exported function (_instead of an inline function_) for [AoT compilation].
157 |
158 | ### Setting up `MetaModule` to use `MetaStaticLoader`
159 |
160 | #### app.module.ts
161 |
162 | ```TypeScript
163 | ...
164 | import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core';
165 | ...
166 |
167 | export function metaFactory(): MetaLoader {
168 | return new MetaStaticLoader({
169 | pageTitlePositioning: PageTitlePositioning.PrependPageTitle,
170 | pageTitleSeparator: ' - ',
171 | applicationName: 'Tour of (lazy/busy) heroes',
172 | defaults: {
173 | title: 'Mighty mighty mouse',
174 | description: 'Mighty Mouse is an animated superhero mouse character',
175 | 'og:image': 'https://upload.wikimedia.org/wikipedia/commons/f/f8/superraton.jpg',
176 | 'og:type': 'website',
177 | 'og:locale': 'en_US',
178 | 'og:locale:alternate': 'en_US,nl_NL,tr_TR'
179 | }
180 | });
181 | }
182 |
183 | ...
184 |
185 | @NgModule({
186 | declarations: [
187 | AppComponent,
188 | ...
189 | ],
190 | ...
191 | imports: [
192 | ...
193 | RouterModule.forRoot(routes),
194 | MetaModule.forRoot({
195 | provide: MetaLoader,
196 | useFactory: (metaFactory)
197 | })
198 | ],
199 | ...
200 | bootstrap: [AppComponent]
201 | })
202 | ```
203 |
204 | `MetaStaticLoader` has one parameter:
205 |
206 | - **providedSettings**: `MetaSettings` : meta settings (_by default, prepend page titles_)
207 |
208 | > :+1: Holy cow! **`@ngx-meta/core`** will update the **page title** and **meta tags** every time the route changes.
209 |
210 | ### Using a `callback` function
211 |
212 | The `MetaStaticLoader` accepts a **`callback`** function to use a custom logic on the meta tag contents (_http-get, [@ngx-translate/core],
213 | etc._).
214 |
215 | > Return type of the **`callback`** function must be **`string`**, **`Observable`** or **`Promise`**.
216 |
217 | When a **`callback`** function is supplied, the `MetaService` will try to **retrieve contents** of meta tags (_except `og:locale`
218 | and `og:locale:alternate`_) using the specified **`callback`**. You can customize the behavior for missing/empty values,
219 | directly from the **`callback`** function itself.
220 |
221 | #### app.module.ts
222 |
223 | ```TypeScript
224 | ...
225 | import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core';
226 | import { TranslateService } from '@ngx-translate/core';
227 | ...
228 |
229 | export function metaFactory(translate: TranslateService): MetaLoader {
230 | return new MetaStaticLoader({
231 | callback: (key: string) => translate.get(key),
232 | pageTitlePositioning: PageTitlePositioning.PrependPageTitle,
233 | pageTitleSeparator: ' - ',
234 | applicationName: 'APP_NAME',
235 | defaults: {
236 | title: 'DEFAULT_TITLE',
237 | description: 'DEFAULT_DESC',
238 | 'og:image': 'https://upload.wikimedia.org/wikipedia/commons/f/f8/superraton.jpg',
239 | 'og:type': 'website',
240 | 'og:locale': 'en_US',
241 | 'og:locale:alternate': 'en_US,nl_NL,tr_TR'
242 | }
243 | });
244 | }
245 |
246 | ...
247 |
248 | @NgModule({
249 | declarations: [
250 | AppComponent,
251 | ...
252 | ],
253 | ...
254 | imports: [
255 | ...
256 | RouterModule.forRoot(routes),
257 | MetaModule.forRoot({
258 | provide: MetaLoader,
259 | useFactory: (metaFactory),
260 | deps: [TranslateService]
261 | })
262 | ],
263 | ...
264 | bootstrap: [AppComponent]
265 | })
266 | ```
267 |
268 | #### app.component.ts
269 |
270 | ```TypeScript
271 | ...
272 | import { MetaService } from '@ngx-meta/core';
273 | ...
274 |
275 | @Component({
276 | ...
277 | })
278 | export class AppComponent implements OnInit {
279 | ...
280 | constructor(private readonly translate: TranslateService,
281 | private readonly meta: MetaService) { }
282 |
283 | ngOnInit(): void {
284 | // add available languages & set default language
285 | this.translate.addLangs(['en', 'tr']);
286 | this.translate.setDefaultLang(defaultLanguage.code);
287 |
288 | this.translate.use('en').subscribe(() => {
289 | this.meta.setTag('og:locale', 'en-US');
290 | });
291 | }
292 | ...
293 | }
294 | ```
295 |
296 | #### home.routes.ts
297 |
298 | ```TypeScript
299 | import { Routes } from '@angular/router';
300 | import { HomeComponent } from './home.component';
301 |
302 | export const routes: Routes = [
303 | {
304 | path: '',
305 | component: HomeComponent,
306 | data: {
307 | meta: {
308 | title: 'PUBLIC.HOME.PAGE_TITLE',
309 | description: 'PUBLIC.HOME.META_DESC'
310 | }
311 | }
312 | }
313 | ];
314 | ```
315 |
316 | You can find out in-depth examples about the use of **`callback`** function on [ng-seed/universal] and on [fulls1z3/example-app],
317 | which are officially maintained seed projects showcasing common patterns and best practices.
318 |
319 | ## Set meta tags programmatically
320 |
321 | ```TypeScript
322 | ...
323 | import { Component, OnInit, OnDestroy } from '@angular/core';
324 | import { MetaService } from '@ngx-meta/core';
325 | ...
326 |
327 | @Component({
328 | ...
329 | })
330 | export class ItemComponent implements OnInit, OnDestroy {
331 | ...
332 | constructor(private readonly meta: MetaService) { }
333 | ...
334 | ngOnInit() {
335 | this.item = //HTTP GET for "item" in the repository
336 | this.meta.setTitle(`Page for ${this.item.name}`);
337 | this.meta.setTag('og:image', this.item.imageUrl);
338 | }
339 | ngOnDestroy() {
340 | this.meta.removeTag('property="og:type"');
341 | }
342 | }
343 |
344 | ```
345 |
346 | ## Credits
347 |
348 | - [ng2-meta](https://github.com/vinaygopinath/ng2-meta): Dynamic meta tags and SEO in Angular2
349 |
350 | ## License
351 |
352 | The MIT License (MIT)
353 |
354 | Copyright (c) 2019 [Burak Tasci]
355 |
356 | [@ngx-translate/core]: https://github.com/ngx-translate/core
357 | [ng-seed/universal]: https://github.com/ng-seed/universal
358 | [fulls1z3/example-app]: https://github.com/fulls1z3/example-app
359 | [@ngx-config/core]: https://github.com/fulls1z3/ngx-config/tree/master/packages/@ngx-config/core
360 | [forroot]: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root
361 | [aot compilation]: https://angular.io/docs/ts/latest/cookbook/aot-compiler.html
362 | [burak tasci]: https://github.com/fulls1z3
363 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../../dist/@ngx-meta/core",
4 | "lib": {
5 | "entryFile": "src/index.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ngx-meta/core",
3 | "version": "9.0.0",
4 | "description": "Dynamic page title & meta tags utility for Angular (w/server-side rendering)",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/fulls1z3/ngx-meta.git"
8 | },
9 | "keywords": [
10 | "open graph",
11 | "page title",
12 | "meta tags",
13 | "server side rendering",
14 | "dom",
15 | "seo",
16 | "meta",
17 | "universal",
18 | "angular"
19 | ],
20 | "author": {
21 | "name": "Burak Tasci",
22 | "email": "mail@buraktasci.com"
23 | },
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/fulls1z3/ngx-meta/core/issues"
27 | },
28 | "homepage": "https://github.com/fulls1z3/ngx-meta/tree/master/packages/@ngx-meta/core#readme",
29 | "peerDependencies": {
30 | "@angular/core": ">=9.0.0 <10.0.0",
31 | "@angular/platform-browser": ">=9.0.0 <10.0.0",
32 | "@angular/platform-browser-dynamic": ">=9.0.0 <10.0.0",
33 | "@angular/router": ">=9.0.0 <10.0.0",
34 | "rxjs": ">=6.0.0"
35 | },
36 | "publishConfig": {
37 | "access": "public",
38 | "directory": "../../../dist/@ngx-meta/core"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './models/meta-settings';
2 | export * from './models/page-title-positioning';
3 | export * from './meta.guard';
4 | export * from './meta.loader';
5 | export * from './meta.module';
6 | export * from './meta.service';
7 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/meta.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { MetaService } from './meta.service';
5 |
6 | @Injectable()
7 | export class MetaGuard implements CanActivate, CanActivateChild {
8 | constructor(private readonly meta: MetaService) {}
9 |
10 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
11 | const url = state.url;
12 |
13 | const metaSettings = route.hasOwnProperty('data') ? route.data.meta : undefined;
14 |
15 | this.meta.update(url, metaSettings);
16 |
17 | return true;
18 | }
19 |
20 | canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
21 | return this.canActivate(route, state);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/meta.loader.ts:
--------------------------------------------------------------------------------
1 | import { MetaSettings } from './models/meta-settings';
2 | import { PageTitlePositioning } from './models/page-title-positioning';
3 |
4 | export abstract class MetaLoader {
5 | abstract get settings(): MetaSettings;
6 | }
7 |
8 | export class MetaStaticLoader implements MetaLoader {
9 | get settings(): MetaSettings {
10 | return this.providedSettings;
11 | }
12 |
13 | constructor(
14 | private readonly providedSettings: MetaSettings = {
15 | pageTitlePositioning: PageTitlePositioning.PrependPageTitle,
16 | defaults: {}
17 | }
18 | ) {}
19 | }
20 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/meta.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
2 |
3 | import { MetaGuard } from './meta.guard';
4 | import { MetaLoader, MetaStaticLoader } from './meta.loader';
5 | import { MetaService } from './meta.service';
6 |
7 | export const metaFactory = () => new MetaStaticLoader();
8 |
9 | @NgModule()
10 | export class MetaModule {
11 | static forRoot(
12 | configuredProvider: any = {
13 | provide: MetaLoader,
14 | useFactory: metaFactory
15 | }
16 | ): ModuleWithProviders {
17 | return {
18 | ngModule: MetaModule,
19 | providers: [configuredProvider, MetaGuard, MetaService]
20 | };
21 | }
22 |
23 | constructor(@Optional() @SkipSelf() parentModule?: MetaModule) {
24 | if (parentModule) {
25 | throw new Error('MetaModule already loaded; import in root module only.');
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/meta.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Meta, Title } from '@angular/platform-browser';
3 | import { from as observableFrom, Observable, of as observableOf } from 'rxjs';
4 |
5 | import { MetaLoader } from './meta.loader';
6 | import { MetaSettings } from './models/meta-settings';
7 | import { PageTitlePositioning } from './models/page-title-positioning';
8 | import { isObservable, isPromise } from './util';
9 |
10 | @Injectable()
11 | export class MetaService {
12 | protected readonly settings: MetaSettings;
13 | private readonly isMetaTagSet: any;
14 |
15 | constructor(readonly loader: MetaLoader, private readonly title: Title, private readonly meta: Meta) {
16 | this.settings = loader.settings;
17 | this.isMetaTagSet = {};
18 | }
19 |
20 | setTitle(title: string, override = false): void {
21 | const title$ = title ? this.callback(title) : observableOf('');
22 |
23 | title$.subscribe((res: string) => {
24 | let fullTitle = '';
25 |
26 | if (!res) {
27 | const defaultTitle$ =
28 | this.settings.defaults && this.settings.defaults.title ? this.callback(this.settings.defaults.title) : observableOf('');
29 |
30 | defaultTitle$.subscribe((defaultTitle: string) => {
31 | if (!override && this.settings.pageTitleSeparator && this.settings.applicationName) {
32 | this.callback(this.settings.applicationName).subscribe((applicationName: string) => {
33 | fullTitle = applicationName ? this.getTitleWithPositioning(defaultTitle, applicationName) : defaultTitle;
34 | this.updateTitle(fullTitle);
35 | });
36 | } else {
37 | this.updateTitle(defaultTitle);
38 | }
39 | });
40 | } else if (!override && this.settings.pageTitleSeparator && this.settings.applicationName) {
41 | this.callback(this.settings.applicationName).subscribe((applicationName: string) => {
42 | fullTitle = applicationName ? this.getTitleWithPositioning(res, applicationName) : res;
43 | this.updateTitle(fullTitle);
44 | });
45 | } else {
46 | this.updateTitle(res);
47 | }
48 | });
49 | }
50 |
51 | setTag(key: string, value: string): void {
52 | if (key === 'title') {
53 | throw new Error(
54 | `Attempt to set ${key} through "setTag": "title" is a reserved tag name. ` + 'Please use `MetaService.setTitle` instead.'
55 | );
56 | }
57 |
58 | const cur = value || (this.settings.defaults && this.settings.defaults[key] ? this.settings.defaults[key] : '');
59 |
60 | const value$ = key !== 'og:locale' && key !== 'og:locale:alternate' ? this.callback(cur) : observableOf(cur);
61 |
62 | value$.subscribe((res: string) => {
63 | this.updateTag(key, res);
64 | });
65 | }
66 |
67 | update(currentUrl: string, metaSettings?: any): void {
68 | if (!metaSettings) {
69 | const fallbackTitle = this.settings.defaults
70 | ? this.settings.defaults.title || this.settings.applicationName
71 | : this.settings.applicationName;
72 |
73 | this.setTitle(fallbackTitle, true);
74 | } else {
75 | if (metaSettings.disabled) {
76 | this.update(currentUrl);
77 |
78 | return;
79 | }
80 |
81 | this.setTitle(metaSettings.title, metaSettings.override);
82 |
83 | Object.keys(metaSettings).forEach(key => {
84 | let value = metaSettings[key];
85 |
86 | if (key === 'title' || key === 'override') {
87 | return;
88 | } else if (key === 'og:locale') {
89 | value = value.replace(/-/g, '_');
90 | } else if (key === 'og:locale:alternate') {
91 | const currentLocale = metaSettings['og:locale'];
92 | this.updateLocales(currentLocale, metaSettings[key]);
93 |
94 | return;
95 | }
96 |
97 | this.setTag(key, value);
98 | });
99 | }
100 |
101 | if (this.settings.defaults) {
102 | Object.keys(this.settings.defaults).forEach(key => {
103 | let value = this.settings.defaults[key];
104 |
105 | if ((metaSettings && (key in this.isMetaTagSet || key in metaSettings)) || key === 'title' || key === 'override') {
106 | return;
107 | } else if (key === 'og:locale') {
108 | value = value.replace(/-/g, '_');
109 | } else if (key === 'og:locale:alternate') {
110 | const currentLocale = metaSettings ? metaSettings['og:locale'] : undefined;
111 | this.updateLocales(currentLocale, value);
112 |
113 | return;
114 | }
115 |
116 | this.setTag(key, value);
117 | });
118 | }
119 |
120 | const baseUrl = this.settings.applicationUrl ? this.settings.applicationUrl : '/';
121 | const url = `${baseUrl}${currentUrl}`.replace(/(https?:\/\/)|(\/)+/g, '$1$2').replace(/\/$/g, '');
122 |
123 | this.setTag('og:url', url ? url : '/');
124 | }
125 |
126 | removeTag(key: string): void {
127 | this.meta.removeTag(key);
128 | }
129 |
130 | private callback(value: string): Observable {
131 | if (this.settings.callback) {
132 | const value$ = this.settings.callback(value);
133 |
134 | if (!isObservable(value$)) {
135 | return isPromise(value$) ? observableFrom(value$) : observableOf(value$);
136 | }
137 |
138 | return value$;
139 | }
140 |
141 | return observableOf(value);
142 | }
143 |
144 | private getTitleWithPositioning(title: string, applicationName: string): string {
145 | switch (this.settings.pageTitlePositioning) {
146 | case PageTitlePositioning.AppendPageTitle:
147 | return applicationName + String(this.settings.pageTitleSeparator) + title;
148 | case PageTitlePositioning.PrependPageTitle:
149 | return title + String(this.settings.pageTitleSeparator) + applicationName;
150 | default:
151 | throw new Error(`Invalid pageTitlePositioning specified [${this.settings.pageTitlePositioning}]!`);
152 | }
153 | }
154 |
155 | private updateTitle(title: string): void {
156 | this.title.setTitle(title);
157 | this.meta.updateTag({
158 | property: 'og:title',
159 | content: title
160 | });
161 | }
162 |
163 | private updateLocales(currentLocale: string, availableLocales: string): void {
164 | const cur = currentLocale || (this.settings.defaults ? this.settings.defaults['og:locale'] : '');
165 |
166 | if (cur && this.settings.defaults) {
167 | this.settings.defaults['og:locale'] = cur.replace(/_/g, '-');
168 | }
169 |
170 | // TODO: set HTML lang attribute - https://github.com/ngx-meta/core/issues/32
171 | // const html = this.document.querySelector('html');
172 | // html.setAttribute('lang', cur);
173 |
174 | const elements = this.meta.getTags('property="og:locale:alternate"');
175 |
176 | elements.forEach((element: any) => {
177 | this.meta.removeTagElement(element);
178 | });
179 |
180 | if (cur && availableLocales) {
181 | availableLocales.split(',').forEach((locale: string) => {
182 | if (cur.replace(/-/g, '_') !== locale.replace(/-/g, '_')) {
183 | this.meta.addTag({
184 | property: 'og:locale:alternate',
185 | content: locale.replace(/-/g, '_')
186 | });
187 | }
188 | });
189 | }
190 | }
191 |
192 | private updateTag(key: string, value: string): void {
193 | if (key.lastIndexOf('og:', 0) === 0) {
194 | this.meta.updateTag({
195 | property: key,
196 | content: key === 'og:locale' ? value.replace(/-/g, '_') : value
197 | });
198 | } else {
199 | this.meta.updateTag({
200 | name: key,
201 | content: value
202 | });
203 | }
204 |
205 | this.isMetaTagSet[key] = true;
206 |
207 | if (key === 'description') {
208 | this.meta.updateTag({
209 | property: 'og:description',
210 | content: value
211 | });
212 | } else if (key === 'author') {
213 | this.meta.updateTag({
214 | property: 'og:author',
215 | content: value
216 | });
217 | } else if (key === 'publisher') {
218 | this.meta.updateTag({
219 | property: 'og:publisher',
220 | content: value
221 | });
222 | } else if (key === 'og:locale') {
223 | const availableLocales = this.settings.defaults ? this.settings.defaults['og:locale:alternate'] : '';
224 |
225 | this.updateLocales(value, availableLocales);
226 | this.isMetaTagSet['og:locale:alternate'] = true;
227 | } else if (key === 'og:locale:alternate') {
228 | const currentLocale = this.meta.getTag('property="og:locale"').content;
229 |
230 | this.updateLocales(currentLocale, value);
231 | this.isMetaTagSet['og:locale'] = true;
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/models/meta-settings.ts:
--------------------------------------------------------------------------------
1 | import { PageTitlePositioning } from './page-title-positioning';
2 |
3 | export interface MetaSettings {
4 | /**
5 | * callback function, to use a custom logic on the meta tag contents (http-get, translate, etc.)
6 | */
7 | callback?: Function;
8 | /**
9 | * represents whether title attributes are positioned before/after the application name
10 | */
11 | pageTitlePositioning: PageTitlePositioning;
12 | /**
13 | * separator character(s) between the title attribute and application name
14 | */
15 | pageTitleSeparator?: string;
16 | /**
17 | * application name, used as a prefix/suffix to the title attribute
18 | */
19 | applicationName?: string;
20 | /**
21 | * application url, used in og:url as a prefix to the current page URL
22 | */
23 | applicationUrl?: string;
24 | /**
25 | * represents a dictionary of default meta tags and their values
26 | */
27 | defaults?: {
28 | /**
29 | * default meta title, used when a route does not have its own title attribute
30 | */
31 | title?: string;
32 | /**
33 | * default meta description, used when a route does not have its own description attribute
34 | */
35 | description?: string;
36 | /**
37 | * default meta keywords, used when a route does not have its own keywords attribute
38 | */
39 | keywords?: string;
40 | /**
41 | * represents a key/value pair of default meta tag and its value
42 | */
43 | [key: string]: string | undefined;
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/models/page-title-positioning.ts:
--------------------------------------------------------------------------------
1 | export enum PageTitlePositioning {
2 | /**
3 | * append page title after application name
4 | */
5 | AppendPageTitle = 0,
6 | /**
7 | * prepend page title before application name
8 | */
9 | PrependPageTitle = 10
10 | }
11 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/src/util.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 |
3 | export const isPromise = (obj: any): obj is Promise => !!obj && typeof obj.then === 'function';
4 |
5 | export const isObservable = (obj: any | Observable): obj is Observable => !!obj && typeof obj.subscribe === 'function';
6 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/tests/common.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed } from '@angular/core/testing';
3 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
4 | import { Routes } from '@angular/router';
5 | import { RouterTestingModule } from '@angular/router/testing';
6 |
7 | import { MetaGuard, MetaModule, MetaService, MetaSettings, PageTitlePositioning } from '../src';
8 |
9 | @Component({ template: ' ' })
10 | export class TestBootstrapComponent {
11 | constructor(private readonly meta: MetaService) {}
12 | }
13 |
14 | @Component({ template: '' })
15 | export class TestComponent {}
16 |
17 | const testRoutes: Routes = [
18 | {
19 | path: '',
20 | canActivateChild: [MetaGuard],
21 | children: [
22 | {
23 | path: '',
24 | component: TestBootstrapComponent,
25 | canActivateChild: [MetaGuard],
26 | children: [
27 | {
28 | path: 'duck',
29 | component: TestComponent,
30 | data: {
31 | meta: {
32 | disabled: true,
33 | title: 'Rubber duckie',
34 | description: 'Have you seen my rubber duckie?'
35 | }
36 | }
37 | },
38 | {
39 | path: 'toothpaste',
40 | component: TestComponent,
41 | data: {
42 | meta: {
43 | title: 'Toothpaste',
44 | override: true,
45 | description: 'Eating toothpaste is considered to be too healthy!',
46 | 'og:locale': 'fr-FR',
47 | 'og:locale:alternate': 'en-US,fr-FR,tr-TR'
48 | }
49 | }
50 | },
51 | {
52 | path: 'no-data',
53 | component: TestComponent
54 | },
55 | {
56 | path: 'no-meta',
57 | component: TestComponent,
58 | data: {
59 | dummy: 'yummy'
60 | }
61 | }
62 | ],
63 | data: {
64 | meta: {
65 | title: 'Sweet home',
66 | description: 'Home, home sweet home... and what?'
67 | }
68 | }
69 | }
70 | ]
71 | }
72 | ];
73 |
74 | export const defaultSettings: MetaSettings = {
75 | pageTitlePositioning: PageTitlePositioning.PrependPageTitle,
76 | defaults: {}
77 | };
78 |
79 | export const emptySettings: MetaSettings = {
80 | pageTitlePositioning: PageTitlePositioning.PrependPageTitle
81 | };
82 |
83 | export const testSettings: MetaSettings = {
84 | pageTitlePositioning: PageTitlePositioning.PrependPageTitle,
85 | pageTitleSeparator: ' - ',
86 | applicationName: 'Tour of (lazy/busy) heroes',
87 | applicationUrl: 'http://localhost:3000',
88 | defaults: {
89 | title: 'Mighty mighty mouse',
90 | description: 'Mighty Mouse is an animated superhero mouse character',
91 | author: 'Mighty Mouse',
92 | publisher: 'a superhero',
93 | 'og:image': 'https://upload.wikimedia.org/wikipedia/commons/f/f8/superraton.jpg',
94 | 'og:type': 'website',
95 | 'og:locale': 'en-US',
96 | 'og:locale:alternate': 'en-US,nl-NL,tr-TR'
97 | }
98 | };
99 |
100 | export const testModuleConfig = (moduleOptions?: any) => {
101 | TestBed.resetTestEnvironment();
102 |
103 | TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()).configureTestingModule({
104 | imports: [RouterTestingModule.withRoutes(testRoutes), MetaModule.forRoot(moduleOptions)],
105 | declarations: [TestBootstrapComponent, TestComponent]
106 | });
107 | };
108 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/tests/meta.loader.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { MetaLoader, MetaService, MetaSettings, MetaStaticLoader } from '../src';
4 |
5 | import { defaultSettings, emptySettings, testModuleConfig, testSettings } from './common';
6 |
7 | describe('@ngx-meta/core:', () => {
8 | beforeEach(() => {
9 | const metaFactory = () => new MetaStaticLoader(testSettings);
10 |
11 | testModuleConfig({
12 | provide: MetaLoader,
13 | useFactory: metaFactory
14 | });
15 | });
16 |
17 | describe('MetaLoader', () => {
18 | it('should be able to return the default settings', () => {
19 | const loader = new MetaStaticLoader();
20 | const loadedApiEndpoint = loader.settings;
21 |
22 | expect(loadedApiEndpoint).toEqual(defaultSettings);
23 | });
24 |
25 | it('should be able to provide `MetaStaticLoader`', () => {
26 | const metaFactory = () => new MetaStaticLoader(testSettings);
27 |
28 | testModuleConfig({
29 | provide: MetaLoader,
30 | useFactory: metaFactory
31 | });
32 |
33 | const meta = TestBed.get(MetaService);
34 |
35 | expect(MetaStaticLoader).toBeDefined();
36 | expect(meta.loader).toBeDefined();
37 | expect(meta.loader instanceof MetaStaticLoader).toBeTruthy();
38 | });
39 |
40 | it('should be able to provide any `MetaLoader`', () => {
41 | class CustomLoader implements MetaLoader {
42 | get settings(): MetaSettings {
43 | return emptySettings;
44 | }
45 | }
46 |
47 | testModuleConfig({
48 | provide: MetaLoader,
49 | useClass: CustomLoader
50 | });
51 |
52 | const meta = TestBed.get(MetaService);
53 |
54 | expect(CustomLoader).toBeDefined();
55 | expect(meta.loader).toBeDefined();
56 | expect(meta.loader instanceof CustomLoader).toBeTruthy();
57 | });
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/tests/meta.service.spec.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:max-file-line-count
2 | import { fakeAsync, inject, TestBed } from '@angular/core/testing';
3 | import { Meta, Title } from '@angular/platform-browser';
4 | import { Router } from '@angular/router';
5 | import { cloneDeep } from 'lodash/fp';
6 | import { of as observableOf } from 'rxjs';
7 |
8 | import { MetaLoader, MetaService, MetaStaticLoader, PageTitlePositioning } from '../src';
9 |
10 | import { defaultSettings, emptySettings, TestBootstrapComponent, testModuleConfig, testSettings } from './common';
11 |
12 | describe('@ngx-meta/core:', () => {
13 | describe('MetaService', () => {
14 | beforeEach(() => {
15 | const settings = cloneDeep(testSettings);
16 | const metaFactory = () => new MetaStaticLoader(settings);
17 |
18 | testModuleConfig({
19 | provide: MetaLoader,
20 | useFactory: metaFactory
21 | });
22 | });
23 |
24 | it('is defined', inject([MetaService], (metaService: MetaService) => {
25 | expect(MetaService).toBeDefined();
26 | expect(metaService).toBeDefined();
27 | expect(metaService instanceof MetaService).toBeTruthy();
28 | }));
29 | });
30 |
31 | describe('MetaService', () => {
32 | beforeEach(() => {
33 | const settings = cloneDeep(testSettings);
34 | const metaFactory = () => new MetaStaticLoader(settings);
35 |
36 | testModuleConfig({
37 | provide: MetaLoader,
38 | useFactory: metaFactory
39 | });
40 | });
41 |
42 | it('should be able to set meta tags using routes', fakeAsync(
43 | inject([Meta, Title], (meta: Meta, title: Title) => {
44 | const router = TestBed.get(Router);
45 |
46 | const fixture = TestBed.createComponent(TestBootstrapComponent);
47 | fixture.detectChanges();
48 |
49 | router.navigate(['/']).then(() => {
50 | expect(title.getTitle()).toEqual('Sweet home - Tour of (lazy/busy) heroes');
51 | expect(meta.getTag('name="description"').content).toEqual('Home, home sweet home... and what?');
52 | expect(meta.getTag('property="og:url"').content).toEqual('http://localhost:3000');
53 |
54 | router.navigate(['/toothpaste']).then(() => {
55 | expect(title.getTitle()).toEqual('Toothpaste');
56 | expect(meta.getTag('name="description"').content).toEqual('Eating toothpaste is considered to be too healthy!');
57 | expect(meta.getTag('property="og:url"').content).toEqual('http://localhost:3000/toothpaste');
58 |
59 | router.navigate(['/duck']).then(() => {
60 | expect(title.getTitle()).toEqual('Mighty mighty mouse');
61 | expect(meta.getTag('name="description"').content).toEqual('Mighty Mouse is an animated superhero mouse character');
62 | expect(meta.getTag('property="og:url"').content).toEqual('http://localhost:3000/duck');
63 |
64 | router.navigate(['/no-data']).then(() => {
65 | expect(title.getTitle()).toEqual('Mighty mighty mouse');
66 | expect(meta.getTag('name="description"').content).toEqual('Mighty Mouse is an animated superhero mouse character');
67 | expect(meta.getTag('property="og:url"').content).toEqual('http://localhost:3000/no-data');
68 |
69 | router.navigate(['/no-meta']).then(() => {
70 | expect(title.getTitle()).toEqual('Mighty mighty mouse');
71 | expect(meta.getTag('name="description"').content).toEqual('Mighty Mouse is an animated superhero mouse character');
72 | expect(meta.getTag('property="og:url"').content).toEqual('http://localhost:3000/no-meta');
73 | });
74 | });
75 | });
76 | });
77 | });
78 | })
79 | ));
80 |
81 | it('should be able to set meta tags using routes w/o `meta` property', fakeAsync(
82 | inject([Meta, Title], (meta: Meta, title: Title) => {
83 | const router = TestBed.get(Router);
84 |
85 | const fixture = TestBed.createComponent(TestBootstrapComponent);
86 | fixture.detectChanges();
87 |
88 | router.navigate(['/no-data']).then(() => {
89 | expect(title.getTitle()).toEqual('Mighty mighty mouse');
90 | expect(meta.getTag('name="description"').content).toEqual('Mighty Mouse is an animated superhero mouse character');
91 | expect(meta.getTag('property="og:url"').content).toEqual('http://localhost:3000/no-data');
92 | });
93 | })
94 | ));
95 |
96 | it('should be able to set meta tags using routes w/o default settings', fakeAsync(
97 | inject([Meta, Title], (meta: Meta, title: Title) => {
98 | const settings = cloneDeep(emptySettings);
99 | const metaFactory = () => new MetaStaticLoader(settings);
100 |
101 | testModuleConfig({
102 | provide: MetaLoader,
103 | useFactory: metaFactory
104 | });
105 |
106 | const router = TestBed.get(Router);
107 |
108 | const fixture = TestBed.createComponent(TestBootstrapComponent);
109 | fixture.detectChanges();
110 |
111 | router.navigate(['/']).then(() => {
112 | expect(title.getTitle()).toEqual('Sweet home');
113 | expect(meta.getTag('name="description"').content).toEqual('Home, home sweet home... and what?');
114 | expect(meta.getTag('property="og:url"').content).toEqual('/');
115 | });
116 | })
117 | ));
118 |
119 | it('should be able to set meta tags using routes w/o default `title` w/o `meta` property', fakeAsync(
120 | inject([Meta, Title], (meta: Meta, title: Title) => {
121 | const settings = cloneDeep(defaultSettings);
122 | settings.applicationName = 'Tour of (lazy/busy) heroes';
123 | settings.defaults = {
124 | description: 'Mighty Mouse is an animated superhero mouse character'
125 | };
126 |
127 | const metaFactory = () => new MetaStaticLoader(settings);
128 |
129 | testModuleConfig({
130 | provide: MetaLoader,
131 | useFactory: metaFactory
132 | });
133 |
134 | const router = TestBed.get(Router);
135 |
136 | const fixture = TestBed.createComponent(TestBootstrapComponent);
137 | fixture.detectChanges();
138 |
139 | router.navigate(['/no-data']).then(() => {
140 | expect(title.getTitle()).toEqual('Tour of (lazy/busy) heroes');
141 | expect(meta.getTag('name="description"').content).toEqual('Mighty Mouse is an animated superhero mouse character');
142 | expect(meta.getTag('property="og:url"').content).toEqual('/no-data');
143 | });
144 | })
145 | ));
146 |
147 | it('should be able to set meta tags using routes w/o default settings w/o `meta` property', fakeAsync(
148 | inject([Meta, Title], (meta: Meta, title: Title) => {
149 | const settings = cloneDeep(emptySettings);
150 | const metaFactory = () => new MetaStaticLoader(settings);
151 |
152 | testModuleConfig({
153 | provide: MetaLoader,
154 | useFactory: metaFactory
155 | });
156 |
157 | const router = TestBed.get(Router);
158 |
159 | const fixture = TestBed.createComponent(TestBootstrapComponent);
160 | fixture.detectChanges();
161 |
162 | router.navigate(['/no-data']).then(() => {
163 | expect(title.getTitle()).toEqual('');
164 | expect(meta.getTag('property="og:url"').content).toEqual('/no-data');
165 | });
166 | })
167 | ));
168 |
169 | it('should be able to set the `title`', fakeAsync(
170 | inject([MetaService, Title], (metaService: MetaService, title: Title) => {
171 | const router = TestBed.get(Router);
172 |
173 | const fixture = TestBed.createComponent(TestBootstrapComponent);
174 | fixture.detectChanges();
175 |
176 | router.navigate(['/']).then(() => {
177 | metaService.setTitle('');
178 | expect(title.getTitle()).toEqual('Mighty mighty mouse - Tour of (lazy/busy) heroes');
179 |
180 | router.navigate(['/no-data']).then(() => {
181 | metaService.setTitle('Mighty tiny mouse');
182 | expect(title.getTitle()).toEqual('Mighty tiny mouse - Tour of (lazy/busy) heroes');
183 |
184 | router.navigate(['/']).then(() => {
185 | metaService.setTitle('Mighty tiny mouse', true);
186 | expect(title.getTitle()).toEqual('Mighty tiny mouse');
187 | });
188 | });
189 | });
190 | })
191 | ));
192 |
193 | it('should be able to set `title` (appended)', fakeAsync(
194 | inject([Title], (title: Title) => {
195 | const settings = cloneDeep(testSettings);
196 | settings.pageTitlePositioning = PageTitlePositioning.AppendPageTitle;
197 |
198 | const metaFactory = () => new MetaStaticLoader(settings);
199 |
200 | testModuleConfig({
201 | provide: MetaLoader,
202 | useFactory: metaFactory
203 | });
204 |
205 | const metaService = TestBed.get(MetaService);
206 | const router = TestBed.get(Router);
207 |
208 | const fixture = TestBed.createComponent(TestBootstrapComponent);
209 | fixture.detectChanges();
210 |
211 | router.navigate(['/']).then(() => {
212 | metaService.setTitle('');
213 | expect(title.getTitle()).toEqual('Tour of (lazy/busy) heroes - Mighty mighty mouse');
214 |
215 | router.navigate(['/no-data']).then(() => {
216 | metaService.setTitle('Mighty tiny mouse');
217 | expect(title.getTitle()).toEqual('Tour of (lazy/busy) heroes - Mighty tiny mouse');
218 |
219 | router.navigate(['/']).then(() => {
220 | metaService.setTitle('Mighty tiny mouse', true);
221 | expect(title.getTitle()).toEqual('Mighty tiny mouse');
222 | });
223 | });
224 | });
225 | })
226 | ));
227 |
228 | it('should be able to set `title` w/o default settings', fakeAsync(
229 | inject([Title], (title: Title) => {
230 | const settings = cloneDeep(defaultSettings);
231 | const metaFactory = () => new MetaStaticLoader(settings);
232 |
233 | testModuleConfig({
234 | provide: MetaLoader,
235 | useFactory: metaFactory
236 | });
237 |
238 | const metaService = TestBed.get(MetaService);
239 | const router = TestBed.get(Router);
240 |
241 | const fixture = TestBed.createComponent(TestBootstrapComponent);
242 | fixture.detectChanges();
243 |
244 | router.navigate(['/']).then(() => {
245 | metaService.setTitle('');
246 | expect(title.getTitle()).toEqual('');
247 | });
248 | })
249 | ));
250 |
251 | it('should be able to set `title` w/o default settings (appended)', fakeAsync(
252 | inject([Title], (title: Title) => {
253 | const settings = cloneDeep(defaultSettings);
254 | settings.pageTitlePositioning = PageTitlePositioning.AppendPageTitle;
255 |
256 | const metaFactory = () => new MetaStaticLoader(settings);
257 |
258 | testModuleConfig({
259 | provide: MetaLoader,
260 | useFactory: metaFactory
261 | });
262 |
263 | const metaService = TestBed.get(MetaService);
264 | const router = TestBed.get(Router);
265 |
266 | const fixture = TestBed.createComponent(TestBootstrapComponent);
267 | fixture.detectChanges();
268 |
269 | router.navigate(['/']).then(() => {
270 | metaService.setTitle('');
271 | expect(title.getTitle()).toEqual('');
272 | });
273 | })
274 | ));
275 |
276 | it('should be able to set `title` as blank if you provide an invalid `PageTitlePositioning`', inject([Title], (title: Title) => {
277 | const settings = cloneDeep(testSettings);
278 | settings.pageTitlePositioning = undefined;
279 |
280 | const metaFactory = () => new MetaStaticLoader(settings);
281 |
282 | testModuleConfig({
283 | provide: MetaLoader,
284 | useFactory: metaFactory
285 | });
286 |
287 | const metaService = TestBed.get(MetaService);
288 | metaService.setTitle('');
289 |
290 | expect(title.getTitle()).toEqual('');
291 | }));
292 |
293 | it('should throw if you attempt to set `title` through `setTag` method', inject([MetaService], (metaService: MetaService) => {
294 | expect(() => {
295 | metaService.setTag('title', '');
296 | }).toThrowError(
297 | 'Attempt to set title through "setTag": "title" is a reserved tag name. ' + 'Please use `MetaService.setTitle` instead.'
298 | );
299 | }));
300 |
301 | it('should be able to set meta `description`', fakeAsync(
302 | inject([MetaService, Meta], (metaService: MetaService, meta: Meta) => {
303 | const router = TestBed.get(Router);
304 |
305 | const fixture = TestBed.createComponent(TestBootstrapComponent);
306 | fixture.detectChanges();
307 |
308 | router.navigate(['/']).then(() => {
309 | metaService.setTag('description', '');
310 | expect(meta.getTag('name="description"').content).toEqual('Mighty Mouse is an animated superhero mouse character');
311 |
312 | router.navigate(['/no-data']).then(() => {
313 | metaService.setTag('description', 'Mighty Mouse is a cool character');
314 | expect(meta.getTag('name="description"').content).toEqual('Mighty Mouse is a cool character');
315 | });
316 | });
317 | })
318 | ));
319 |
320 | it('should be able to set meta `description` w/o default settings', fakeAsync(
321 | inject([Meta], (meta: Meta) => {
322 | const settings = cloneDeep(emptySettings);
323 | const metaFactory = () => new MetaStaticLoader(settings);
324 |
325 | testModuleConfig({
326 | provide: MetaLoader,
327 | useFactory: metaFactory
328 | });
329 |
330 | const metaService = TestBed.get(MetaService);
331 | const router = TestBed.get(Router);
332 |
333 | const fixture = TestBed.createComponent(TestBootstrapComponent);
334 | fixture.detectChanges();
335 |
336 | router.navigate(['/']).then(() => {
337 | metaService.setTag('description', '');
338 | expect(meta.getTag('name="description"').content).toEqual('');
339 | });
340 | })
341 | ));
342 |
343 | it('should be able to set meta `author`', fakeAsync(
344 | inject([MetaService, Meta], (metaService: MetaService, meta: Meta) => {
345 | const router = TestBed.get(Router);
346 |
347 | const fixture = TestBed.createComponent(TestBootstrapComponent);
348 | fixture.detectChanges();
349 |
350 | router.navigate(['/']).then(() => {
351 | metaService.setTag('author', '');
352 | expect(meta.getTag('name="author"').content).toEqual('Mighty Mouse');
353 |
354 | router.navigate(['/no-data']).then(() => {
355 | metaService.setTag('author', 'Mickey Mouse');
356 | expect(meta.getTag('name="author"').content).toEqual('Mickey Mouse');
357 | });
358 | });
359 | })
360 | ));
361 |
362 | it('should be able to set meta `publisher`', fakeAsync(
363 | inject([MetaService, Meta], (metaService: MetaService, meta: Meta) => {
364 | const router = TestBed.get(Router);
365 |
366 | const fixture = TestBed.createComponent(TestBootstrapComponent);
367 | fixture.detectChanges();
368 |
369 | router.navigate(['/']).then(() => {
370 | metaService.setTag('publisher', '');
371 | expect(meta.getTag('name="publisher"').content).toEqual('a superhero');
372 |
373 | router.navigate(['/no-data']).then(() => {
374 | metaService.setTag('publisher', 'another superhero');
375 | expect(meta.getTag('name="publisher"').content).toEqual('another superhero');
376 | });
377 | });
378 | })
379 | ));
380 |
381 | it('should be able to set `og:locale`', fakeAsync(
382 | inject([MetaService, Meta], (metaService: MetaService, meta: Meta) => {
383 | const router = TestBed.get(Router);
384 |
385 | const fixture = TestBed.createComponent(TestBootstrapComponent);
386 | fixture.detectChanges();
387 |
388 | router.navigate(['/']).then(() => {
389 | metaService.setTag('og:locale', '');
390 | expect(meta.getTag('property="og:locale"').content).toEqual('en_US');
391 |
392 | let elements = meta.getTags('property="og:locale:alternate"');
393 |
394 | expect(elements.length).toEqual(2);
395 | expect(elements[0].content).toEqual('nl_NL');
396 | expect(elements[1].content).toEqual('tr_TR');
397 |
398 | router.navigate(['/no-data']).then(() => {
399 | metaService.setTag('og:locale', 'tr-TR');
400 | expect(meta.getTag('property="og:locale"').content).toEqual('tr_TR');
401 |
402 | elements = meta.getTags('property="og:locale:alternate"');
403 |
404 | expect(elements.length).toEqual(2);
405 | expect(elements[0].content).toEqual('en_US');
406 | expect(elements[1].content).toEqual('nl_NL');
407 | });
408 | });
409 | })
410 | ));
411 |
412 | it('should be able to set `og:locale:alternate` w/ `og:locale:alternate`', fakeAsync(
413 | inject([MetaService, Meta], (metaService: MetaService, meta: Meta) => {
414 | const router = TestBed.get(Router);
415 |
416 | const fixture = TestBed.createComponent(TestBootstrapComponent);
417 | fixture.detectChanges();
418 |
419 | router.navigate(['/']).then(() => {
420 | metaService.setTag('og:locale:alternate', '');
421 | const elements = meta.getTags('property="og:locale:alternate"');
422 |
423 | expect(elements.length).toEqual(2);
424 | expect(elements[0].content).toEqual('nl_NL');
425 | expect(elements[1].content).toEqual('tr_TR');
426 |
427 | router.navigate(['/no-data']).then(() => {
428 | metaService.setTag('og:locale:alternate', 'tr-TR');
429 | expect(meta.getTag('property="og:locale:alternate"').content).toEqual('tr_TR');
430 | });
431 | });
432 | })
433 | ));
434 |
435 | it('should be able to set `og:locale` w/o default settings', fakeAsync(
436 | inject([Meta], (meta: Meta) => {
437 | const settings = cloneDeep(emptySettings);
438 | const metaFactory = () => new MetaStaticLoader(settings);
439 |
440 | testModuleConfig({
441 | provide: MetaLoader,
442 | useFactory: metaFactory
443 | });
444 |
445 | const router = TestBed.get(Router);
446 | const metaService = TestBed.get(MetaService);
447 |
448 | const fixture = TestBed.createComponent(TestBootstrapComponent);
449 | fixture.detectChanges();
450 |
451 | router.navigate(['/']).then(() => {
452 | metaService.setTag('og:locale', '');
453 | expect(meta.getTag('property="og:locale"').content).toEqual('');
454 |
455 | router.navigate(['/no-data']).then(() => {
456 | metaService.setTag('og:locale', 'tr-TR');
457 | expect(meta.getTag('property="og:locale"').content).toEqual('tr_TR');
458 | });
459 | });
460 | })
461 | ));
462 |
463 | it('should be able to do not set `og:locale:alternate` as current `og:locale`', inject([Meta], (meta: Meta) => {
464 | const settings = cloneDeep(defaultSettings);
465 | settings.defaults['og:locale'] = 'tr-TR';
466 |
467 | const metaFactory = () => new MetaStaticLoader(settings);
468 |
469 | testModuleConfig({
470 | provide: MetaLoader,
471 | useFactory: metaFactory
472 | });
473 |
474 | const metaService = TestBed.get(MetaService);
475 |
476 | expect(meta.getTag('property="og:locale"').content).toEqual('tr_TR');
477 |
478 | metaService.setTag('og:locale:alternate', 'tr-TR');
479 | expect(meta.getTag('property="og:locale:alternate"')).toBeNull();
480 | }));
481 |
482 | it('should be able to do not set `og:locale:alternate` using routes w/o default settings & w/o `og:locale`', fakeAsync(
483 | inject([Meta, Title], (meta: Meta, title: Title) => {
484 | const settings = cloneDeep(defaultSettings);
485 | settings.defaults['og:locale:alternate'] = 'en-US';
486 |
487 | const metaFactory = () => new MetaStaticLoader(settings);
488 |
489 | testModuleConfig({
490 | provide: MetaLoader,
491 | useFactory: metaFactory
492 | });
493 |
494 | const router = TestBed.get(Router);
495 |
496 | const fixture = TestBed.createComponent(TestBootstrapComponent);
497 | fixture.detectChanges();
498 |
499 | router.navigate(['/']).then(() => {
500 | expect(title.getTitle()).toEqual('Sweet home');
501 | expect(meta.getTag('name="description"').content).toEqual('Home, home sweet home... and what?');
502 | expect(meta.getTag('property="og:url"').content).toEqual('/');
503 | expect(meta.getTag('property="og:locale:alternate"')).toBeNull();
504 | });
505 | })
506 | ));
507 |
508 | it('should be able to set any other meta tag', fakeAsync(
509 | inject([MetaService, Meta], (metaService: MetaService, meta: Meta) => {
510 | const router = TestBed.get(Router);
511 |
512 | const fixture = TestBed.createComponent(TestBootstrapComponent);
513 | fixture.detectChanges();
514 |
515 | router.navigate(['/']).then(() => {
516 | metaService.setTag('og:type', '');
517 | expect(meta.getTag('property="og:type"').content).toEqual('website');
518 |
519 | router.navigate(['/no-data']).then(() => {
520 | metaService.setTag('og:type', 'blog');
521 | expect(meta.getTag('property="og:type"').content).toEqual('blog');
522 | });
523 | });
524 | })
525 | ));
526 |
527 | it('should be able to remove tag', fakeAsync(
528 | inject([MetaService, Meta], (metaService: MetaService, meta: Meta) => {
529 | const router = TestBed.get(Router);
530 |
531 | const fixture = TestBed.createComponent(TestBootstrapComponent);
532 | fixture.detectChanges();
533 |
534 | router.navigate(['/']).then(() => {
535 | metaService.removeTag('property="og:type"');
536 | expect(meta.getTag('property="og:type"')).toBeNull();
537 | });
538 | })
539 | ));
540 | });
541 |
542 | describe('MetaService w/callback', () => {
543 | it('should be able to set `title` w/`non-observable` callback', fakeAsync(
544 | inject([Title], (title: Title) => {
545 | const callback = (value: string) => (value === 'Tour of (lazy/busy) heroes' ? '' : value);
546 |
547 | const settings = cloneDeep(testSettings);
548 | settings.callback = callback;
549 | const metaFactory = () => new MetaStaticLoader(settings);
550 |
551 | testModuleConfig({
552 | provide: MetaLoader,
553 | useFactory: metaFactory
554 | });
555 |
556 | const metaService = TestBed.get(MetaService);
557 | const router = TestBed.get(Router);
558 |
559 | const fixture = TestBed.createComponent(TestBootstrapComponent);
560 | fixture.detectChanges();
561 |
562 | router.navigate(['/']).then(() => {
563 | metaService.setTitle('test');
564 | expect(title.getTitle()).toEqual('test');
565 | });
566 | })
567 | ));
568 |
569 | it('should be able to set `title` w/`non-observable` callback w/o default settings', fakeAsync(
570 | inject([Title], (title: Title) => {
571 | const callback = (value: string) => (value === 'Tour of (lazy/busy) heroes' ? '' : value);
572 |
573 | const settings = cloneDeep(defaultSettings);
574 | settings.callback = callback;
575 | settings.pageTitleSeparator = ' - ';
576 | settings.applicationName = 'Tour of (lazy/busy) heroes';
577 | settings.defaults.title = 'test';
578 | const metaFactory = () => new MetaStaticLoader(settings);
579 |
580 | testModuleConfig({
581 | provide: MetaLoader,
582 | useFactory: metaFactory
583 | });
584 |
585 | const metaService = TestBed.get(MetaService);
586 | const router = TestBed.get(Router);
587 |
588 | const fixture = TestBed.createComponent(TestBootstrapComponent);
589 | fixture.detectChanges();
590 |
591 | router.navigate(['/']).then(() => {
592 | metaService.setTitle('');
593 | expect(title.getTitle()).toEqual('test');
594 | });
595 | })
596 | ));
597 |
598 | it('should be able to set meta tags w/`promise` callback', fakeAsync(
599 | inject([Title], (title: Title) => {
600 | const callback = async (value: string) => Promise.resolve(value);
601 |
602 | const settings = cloneDeep(testSettings);
603 | settings.callback = callback;
604 | const metaFactory = () => new MetaStaticLoader(settings);
605 |
606 | testModuleConfig({
607 | provide: MetaLoader,
608 | useFactory: metaFactory
609 | });
610 |
611 | const router = TestBed.get(Router);
612 |
613 | const fixture = TestBed.createComponent(TestBootstrapComponent);
614 | fixture.detectChanges();
615 |
616 | router.navigate(['/']).then(() => {
617 | expect(title.getTitle()).toEqual('Sweet home - Tour of (lazy/busy) heroes');
618 | });
619 | })
620 | ));
621 |
622 | it('should be able to set meta tags w/`observable` callback', fakeAsync(
623 | inject([Title], (title: Title) => {
624 | const settings = cloneDeep(testSettings);
625 | settings.callback = observableOf;
626 | const metaFactory = () => new MetaStaticLoader(settings);
627 |
628 | testModuleConfig({
629 | provide: MetaLoader,
630 | useFactory: metaFactory
631 | });
632 |
633 | const router = TestBed.get(Router);
634 |
635 | const fixture = TestBed.createComponent(TestBootstrapComponent);
636 | fixture.detectChanges();
637 |
638 | router.navigate(['/']).then(() => {
639 | expect(title.getTitle()).toEqual('Sweet home - Tour of (lazy/busy) heroes');
640 | });
641 | })
642 | ));
643 | });
644 | });
645 |
--------------------------------------------------------------------------------
/packages/@ngx-meta/core/tests/util.spec.ts:
--------------------------------------------------------------------------------
1 | import { of as observableOf } from 'rxjs';
2 |
3 | import { isObservable, isPromise } from '../src/util';
4 |
5 | describe('@ngx-meta/core:', () => {
6 | describe('isPromise', () => {
7 | it('should be true for native Promises', () => {
8 | expect(isPromise(Promise.resolve(true))).toEqual(true);
9 | });
10 |
11 | it('should be true for thenables', () => {
12 | expect(
13 | isPromise({
14 | then: () => {
15 | // NOTE: go on
16 | }
17 | })
18 | ).toEqual(true);
19 | });
20 |
21 | it('should be false if "then" is not a function', () => {
22 | expect(isPromise({ then: 0 })).toEqual(false);
23 | });
24 |
25 | it('should be false if the argument has no "then" function', () => {
26 | expect(isPromise({})).toEqual(false);
27 | });
28 |
29 | it('should be false if the argument is undefined or null', () => {
30 | expect(isPromise(undefined)).toEqual(false);
31 | expect(isPromise(null)).toEqual(false);
32 | });
33 | });
34 |
35 | describe('isObservable', () => {
36 | it('should be true for an Observable', () => {
37 | expect(isObservable(observableOf(true))).toEqual(true);
38 | });
39 |
40 | it('should be false if the argument is undefined', () => {
41 | expect(isObservable(undefined)).toEqual(false);
42 | });
43 |
44 | it('should be false if the argument is null', () => {
45 | expect(isObservable(null)).toEqual(false);
46 | });
47 |
48 | it('should be false if the argument is an object', () => {
49 | expect(isObservable({})).toEqual(false);
50 | });
51 |
52 | it('should be false if the argument is a function', () => {
53 | expect(
54 | isObservable(() => {
55 | // NOTE: go on
56 | })
57 | ).toEqual(false);
58 | });
59 |
60 | // TODO use Symbol.observable when https://github.com/ReactiveX/rxjs/issues/2415 will be resolved
61 | // it('should be false if the argument is the object with subscribe function',
62 | // () => {
63 | // expect(isObservable({
64 | // subscribe: () => {
65 | // }
66 | // }))
67 | // .toEqual(false);
68 | // });
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/tools/build/helpers.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable
2 | import * as path from 'path';
3 |
4 | export function root(args: any = ''): string {
5 | const ROOT = path.resolve(__dirname, '../..');
6 | args = [].slice.call(arguments, 0);
7 |
8 | return path.join.apply(path, [ROOT].concat(args));
9 | }
10 |
--------------------------------------------------------------------------------
/tools/build/packager.ts:
--------------------------------------------------------------------------------
1 | import { ngPackagr } from 'ng-packagr';
2 |
3 | import { root } from './helpers';
4 |
5 | ngPackagr()
6 | .forProject(root(`./packages/@ngx-meta/${process.argv[2]}/ng-package.json`))
7 | .withTsConfig(root('./tools/build/tsconfig.package.json'))
8 | .build()
9 | .catch(() => (process.exitCode = 1));
10 |
--------------------------------------------------------------------------------
/tools/build/tsconfig.package.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es2015",
5 | "module": "es2015",
6 | "noImplicitAny": true,
7 | "suppressImplicitAnyIndexErrors": true,
8 | "importHelpers": true,
9 | "sourceMap": true,
10 | "inlineSources": true,
11 | "declaration": true,
12 | "removeComments": true,
13 | "stripInternal": true,
14 | "rootDir": "./",
15 | "baseUrl": "",
16 | "paths": {
17 | "@ngx-meta/*": ["../../dist/@ngx-meta/*"]
18 | },
19 | "typeRoots": ["../../node_modules/@types"],
20 | "lib": [
21 | "es2015",
22 | "dom"
23 | ]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tools/test/jest.setup.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable
2 | import 'jest-preset-angular';
3 |
4 | const mock = () => {
5 | let storage = {};
6 |
7 | return {
8 | getItem: (key: string) => (key in storage ? (storage as any)[key] : undefined),
9 | setItem: (key: string, value: any) => ((storage as any)[key] = value || ''),
10 | removeItem: (key: string) => delete (storage as any)[key],
11 | clear: () => (storage = {})
12 | };
13 | };
14 |
15 | Object.defineProperty(window, 'CSS', { value: mock() });
16 | Object.defineProperty(window, 'localStorage', { value: mock() });
17 | Object.defineProperty(window, 'sessionStorage', { value: mock() });
18 |
19 | Object.defineProperty(document, 'doctype', {
20 | value: ''
21 | });
22 |
23 | Object.defineProperty(window, 'getComputedStyle', {
24 | value: () => {
25 | return {
26 | display: 'none',
27 | appearance: ['-webkit-appearance']
28 | };
29 | }
30 | });
31 |
--------------------------------------------------------------------------------
/tools/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "../tslint.json"
4 | ],
5 | "rules": {
6 | "no-implicit-dependencies": [
7 | true,
8 | "dev"
9 | ],
10 | "no-dynamic-delete": false
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": ".",
4 | "sourceMap": true,
5 | "declaration": false,
6 | "moduleResolution": "node",
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "importHelpers": true,
10 | "target": "es2015",
11 | "module": "commonjs",
12 | "typeRoots": ["node_modules/@types"],
13 | "lib": ["es2017", "dom"],
14 | "skipLibCheck": true,
15 | "skipDefaultLibCheck": true,
16 | "baseUrl": ".",
17 | "paths": {
18 | "@ngx-meta/core": ["packages/@ngx-meta/core/src/index.ts"]
19 | },
20 | "noImplicitAny": true,
21 | "suppressImplicitAnyIndexErrors": true
22 | },
23 | "include": ["tools/**/*.ts", "packages/**/*.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.lint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es2015",
5 | "strictNullChecks": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": ["node_modules/codelyzer"],
3 | "extends": ["angular-tslint-rules", "tslint-config-prettier"],
4 | "rules": {
5 | "no-implicit-dependencies": [true, "dev", ["lodash"]],
6 | "ordered-imports": [
7 | true,
8 | {
9 | "import-sources-order": "case-insensitive",
10 | "named-imports-order": "case-insensitive",
11 | "grouped-imports": true
12 | }
13 | ],
14 | "no-null-keyword": false
15 | }
16 | }
17 |
--------------------------------------------------------------------------------