Compare commits
28 Commits
backup-7.2
...
ci/github-
Author | SHA1 | Date | |
---|---|---|---|
3be276c78b | |||
13eb57a59f | |||
0b3ae3d70c | |||
4b67b0af3e | |||
bba5e2632e | |||
c5ce4e62c6 | |||
509aa61619 | |||
cdd737e28b | |||
5da55d6246 | |||
50a91ba28c | |||
077a5fb04b | |||
bc0ee01d09 | |||
326b464d20 | |||
8a7498e0ef | |||
07ada7f3d9 | |||
880e8a5cfc | |||
a0585c9a9a | |||
a833b98fd0 | |||
4b70a4e905 | |||
f2a1c66031 | |||
cfb8c17511 | |||
99d0e27587 | |||
b08f3acf09 | |||
3ab25ab078 | |||
0604527199 | |||
8f8572fd3e | |||
e8f7241366 | |||
a20b2f72f2 |
758
.github/CODEOWNERS
vendored
Normal file
758
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1,758 @@
|
||||
# ==================================================================================
|
||||
# ==================================================================================
|
||||
# Angular CODEOWNERS
|
||||
# ==================================================================================
|
||||
# ==================================================================================
|
||||
#
|
||||
# Configuration of code ownership and review approvals for the angular/angular repo.
|
||||
#
|
||||
# More info: https://help.github.com/articles/about-codeowners/
|
||||
#
|
||||
|
||||
|
||||
# ================================================
|
||||
# General rules / philosophy
|
||||
# ================================================
|
||||
#
|
||||
# - we trust that people do the right thing and not approve changes they don't feel confident reviewing
|
||||
# - we use github teams so that we funnel code reviews to the most appropriate reviewer, this is why the team structure is fine-grained
|
||||
# - we enforce that only approved PRs get merged to ensure that unreviewed code doesn't get accidentaly merged
|
||||
# - we delegate approval rights as much as possible so that we can scale better
|
||||
# - each group must have at least one person, but several people are preferable to avoid a single point of failure issues
|
||||
# - most file groups have one or two global approvers groups as fallbacks:
|
||||
# - @angular/fw-global-approvers: for approving minor changes, large-scale refactorings, and emergency situations.
|
||||
# - @angular/fw-global-approvers-for-docs-only-changes: for approving minor documentation-only changes that don't require engineering review
|
||||
# - a small number of file groups have very limited number of reviewers because incorrect changes to the files they guard would have serious consequences (e.g. security, public api)
|
||||
#
|
||||
# Configuration nuances:
|
||||
#
|
||||
# - This configuration works in conjunction with the protected branch settings that require all changes to be made via pull requests with at least one approval.
|
||||
# - This approval can come from an appropriate codeowner, or any repo collaborator (person with write access) if the PR is authored by a codeowner.
|
||||
# - Each codeowners team must have write access to the repo, otherwise their reviewes won't count.
|
||||
#
|
||||
# In the case of emergency, the repo administrators which include angular-caretaker can bypass this requirement.
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# GitHub username registry
|
||||
# (just to make this file easier to understand)
|
||||
# ================================================
|
||||
|
||||
# alexeagle - Alex Eagle
|
||||
# alxhub - Alex Rickabaugh
|
||||
# AndrewKushnir - Andrew Kushnir
|
||||
# andrewseguin - Andrew Seguin
|
||||
# benlesh - Ben Lesh
|
||||
# brandonroberts - Brandon Roberts
|
||||
# filipesilva - Filipe Silva
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
# IgorMinar - Igor Minar
|
||||
# jasonaden - Jason Aden
|
||||
# jenniferfell - Jennifer Fell
|
||||
# kara - Kara Erickson
|
||||
# kyliau - Keen Yee Liau
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
# ocombe - Olivier Combe
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
# pkozlowski-opensource - Pawel Kozlowski
|
||||
# robwormald - Rob Wormald
|
||||
# stephenfluin - Stephen Fluin
|
||||
# vikerman - Vikram Subramanian
|
||||
|
||||
|
||||
|
||||
######################################################################################################
|
||||
#
|
||||
# Team structure and memberships
|
||||
# ------------------------------
|
||||
#
|
||||
# This section is here just because the GitHub UI is too hard to navigate and audit.
|
||||
#
|
||||
# Any changes to team structure or memberships must first be made in this file and only then
|
||||
# implemented in the GitHub UI.
|
||||
#######################################################################################################
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/framework-global-approvers
|
||||
# ===========================================================
|
||||
# Used for approving minor changes, large-scale refactorings, and emergency situations.
|
||||
# (secret team to avoid review requests, it also doesn't inherit from @angular/framework because nested teams can't be secret)
|
||||
#
|
||||
# - IgorMinar
|
||||
# - kara
|
||||
# - mhevery
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/framework-global-approvers-for-docs-only-changes
|
||||
# ===========================================================
|
||||
# Used for approving minor documentation-only changes that don't require engineering review.
|
||||
# (secret team to avoid review requests, it also doesn't inherit from @angular/framework because nested teams can't be secret)
|
||||
#
|
||||
# - gkalpak
|
||||
# - jenniferfell
|
||||
# - petebacondarwin
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-animations
|
||||
# ===========================================================
|
||||
#
|
||||
# - matsko
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/tools-bazel
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - kyliau
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/tools-cli
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - filipesilva
|
||||
# - hansl
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-compiler
|
||||
# ===========================================================
|
||||
#
|
||||
# - alxhub
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-ngcc
|
||||
# ===========================================================
|
||||
#
|
||||
# - alxhub
|
||||
# - gkalpak
|
||||
# - petebacondarwin
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-core
|
||||
# ===========================================================
|
||||
#
|
||||
# - alxhub
|
||||
# - AndrewKushnir
|
||||
# - kara
|
||||
# - mhevery
|
||||
# - pkozlowski-opensource
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-http
|
||||
# ===========================================================
|
||||
#
|
||||
# - alxhub
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-elements
|
||||
# ===========================================================
|
||||
#
|
||||
# - andrewseguin
|
||||
# - gkalpak
|
||||
# - robwormald
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-forms
|
||||
# ===========================================================
|
||||
#
|
||||
# - kara
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/tools-language-service
|
||||
# ===========================================================
|
||||
#
|
||||
# - kyliau
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-server
|
||||
# ===========================================================
|
||||
#
|
||||
# - alxhub
|
||||
# - vikerman
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-router
|
||||
# ===========================================================
|
||||
#
|
||||
# - jasonaden
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-service-worker
|
||||
# ===========================================================
|
||||
#
|
||||
# - alxhub
|
||||
# - gkalpak
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-upgrade
|
||||
# ===========================================================
|
||||
#
|
||||
# - gkalpak
|
||||
# - petebacondarwin
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-testing
|
||||
# ===========================================================
|
||||
#
|
||||
# - vikerman
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-i18n
|
||||
# ===========================================================
|
||||
#
|
||||
# - AndrewKushnir
|
||||
# - mhevery
|
||||
# - ocombe
|
||||
# - vikerman
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-security
|
||||
# ===========================================================
|
||||
#
|
||||
# - IgorMinar
|
||||
# - mhevery
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/tools-benchpress
|
||||
# ===========================================================
|
||||
#
|
||||
# - alxhub
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-integration
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - IgorMinar
|
||||
# - mhevery
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/docs-infra
|
||||
# ===========================================================
|
||||
#
|
||||
# - gkalpak
|
||||
# - IgorMinar
|
||||
# - petebacondarwin
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-docs-intro
|
||||
# ===========================================================
|
||||
#
|
||||
# - brandonroberts
|
||||
# - IgorMinar
|
||||
# - stephenfluin
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-docs-observables
|
||||
# ===========================================================
|
||||
#
|
||||
# - benlesh
|
||||
# - jasonaden
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-docs-packaging
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-docs-marketing
|
||||
# ===========================================================
|
||||
#
|
||||
# - IgorMinar
|
||||
# - stephenfluin
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-public-api
|
||||
# ===========================================================
|
||||
#
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-dev-infra
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
######################################################################################################
|
||||
#
|
||||
# CODEOWNERS rules
|
||||
# -----------------
|
||||
#
|
||||
# All the following rules are applied in the order specified in this file.
|
||||
# The last rule that matches wins!
|
||||
#
|
||||
# See https://git-scm.com/docs/gitignore#_pattern_format for pattern syntax docs.
|
||||
#
|
||||
######################################################################################################
|
||||
|
||||
|
||||
# ================================================
|
||||
# Default Owners
|
||||
# (in case no pattern matches a path in a PR - this should be treated as a bug and result in adding the path to CODEOWNERS)
|
||||
# ================================================
|
||||
|
||||
* @IgorMinar @angular/framework-global-approvers
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/animations
|
||||
# ================================================
|
||||
|
||||
/packages/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-browser/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/animations.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/bazel
|
||||
# ================================================
|
||||
|
||||
/packages/bazel/** @angular/tools-bazel @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/compiler
|
||||
# @angular/compiler-cli
|
||||
# ================================================
|
||||
|
||||
/packages/compiler/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/compiler-cli/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/aot-compiler.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# packages/compiler-cli/src/ngcc/
|
||||
# ================================================
|
||||
|
||||
/packages/compiler-cli/src/ngcc/** @angular/fw-ngcc @angular/framework-global-approvers
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/compiler-cli/ngtools
|
||||
#
|
||||
# a rule to control API changes between @angular/compiler-cli and @angular/cli
|
||||
# ================================================
|
||||
|
||||
/packages/compiler-cli/src/ngtools/** @angular/tools-cli @angular/framework-global-approvers
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/core
|
||||
# @angular/common (except @angular/common/http)
|
||||
# @angular/platform-browser
|
||||
# @angular/platform-browser-dynamic
|
||||
# @angular/platform-webworker
|
||||
# ================================================
|
||||
|
||||
/packages/core/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-browser/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-browser-dynamic/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-webworker/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/attribute-directives.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/attribute-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/attribute-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/bootstrapping.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/bootstrapping/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/component-interaction.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/component-interaction/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/component-interaction/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/component-styles.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/component-styles/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/dependency-injection.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/dependency-injection/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/dependency-injection/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/dependency-injection-in-action.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/dependency-injection-in-action/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/dependency-injection-in-action/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/dependency-injection-pattern.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/dynamic-component-loader.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/dynamic-component-loader/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/dynamic-component-loader/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/entry-components.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/feature-modules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/feature-modules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/feature-modules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/frequent-ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/frequent-ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/hierarchical-dependency-injection.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/hierarchical-dependency-injection/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/lazy-loading-ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/lazy-loading-ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/lazy-loading-ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/lifecycle-hooks.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/lifecycle-hooks/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/lifecycle-hooks/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/examples/ngcontainer/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/ngcontainer/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/ngmodule/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/ngmodule/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/ngmodule-api.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/ngmodule-faq.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/ngmodule-faq/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/ngmodule-vs-jsmodule.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/module-types.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/template-syntax.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/pipes.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/providers.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/providers/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/singleton-services.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/set-document-title.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/set-document-title/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/set-document-title/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/sharing-ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/structural-directives.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/structural-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/structural-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/common/http
|
||||
# ================================================
|
||||
|
||||
/packages/common/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/http.md @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/elements
|
||||
# ================================================
|
||||
|
||||
/packages/elements/** @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/elements/** @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/elements/** @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/elements.md @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/forms
|
||||
# ================================================
|
||||
|
||||
/packages/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/forms.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/forms-overview.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/forms-overview/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/forms-overview/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/form-validation.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/form-validation/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/form-validation/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/dynamic-form.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/dynamic-form/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/dynamic-form/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/reactive-forms.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/reactive-forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/reactive-forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/language-service
|
||||
# ================================================
|
||||
|
||||
/packages/language-service/** @angular/tools-language-service @angular/framework-global-approvers
|
||||
/aio/content/guide/language-service.md @angular/tools-language-service @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/language-service/** @angular/tools-language-service @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/platform-server
|
||||
# ================================================
|
||||
|
||||
/packages/platform-server/** @angular/fw-server @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/universal.md @angular/fw-server @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/universal/** @angular/fw-server @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/router
|
||||
# ================================================
|
||||
|
||||
/packages/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/router.md @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/service-worker
|
||||
# ================================================
|
||||
|
||||
/packages/service-worker/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/service-worker-getting-started.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/service-worker-getting-started/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/service-worker-communications.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/service-worker-config.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/service-worker-devops.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/service-worker-intro.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/service-worker/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/upgrade
|
||||
# ================================================
|
||||
|
||||
/packages/upgrade/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/upgrade/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/upgrade.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/upgrade-module/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/upgrade/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/upgrade-phonecat-1-typescript/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/upgrade-phonecat-2-hybrid/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/upgrade-phonecat-3-final/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/upgrade-performance.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/ajs-quick-reference.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/ajs-quick-reference/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/**/testing
|
||||
# ================================================
|
||||
|
||||
testing/** @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/testing.md @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/testing/** @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/testing/** @angular/fw-testing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular i18n
|
||||
# ================================================
|
||||
|
||||
/packages/core/src/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/core/src/render3/i18n.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/core/src/render3/i18n.md @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/core/src/render3/interfaces/i18n.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/locales/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/src/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/src/pipes/date_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/src/pipes/i18n_plural_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/src/pipes/i18n_select_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/src/pipes/number_pipe.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/compiler/src/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/compiler/src/render3/view/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/compiler-cli/src/extract_i18n.ts @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/i18n.md @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular security
|
||||
# ================================================
|
||||
|
||||
/packages/core/src/sanitization/** @angular/fw-security
|
||||
/packages/core/test/linker/security_integration_spec.ts @angular/fw-security
|
||||
/packages/compiler/src/schema/** @angular/fw-security
|
||||
/packages/platform-browser/src/security/** @angular/fw-security
|
||||
/aio/content/guide/security.md @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/security/** @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# benchpress
|
||||
# ================================================
|
||||
|
||||
/packages/benchpress/** @angular/tools-benchpress @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# /integration/*
|
||||
# ================================================
|
||||
|
||||
/integration/** @angular/fw-integration @angular/framework-global-approvers
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# docs-infra
|
||||
# ================================================
|
||||
|
||||
/aio/* @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/aio-builds-setup/** @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/scripts/** @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/src/** @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/tests/** @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/tools/** @angular/docs-infra @angular/framework-global-approvers
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Docs: getting started & tutorial
|
||||
# ================================================
|
||||
|
||||
/aio/content/guide/quickstart.md @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/tutorial/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Docs: observables
|
||||
# ================================================
|
||||
|
||||
/aio/content/examples/observables/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/observables/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/observables.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/comparing-observables.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/observables-in-angular/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/observables-in-angular/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/observables-in-angular.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/practical-observable-usage/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/practical-observable-usage.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/rx-library/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/rx-library.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Docs: packaging, tooling, releasing
|
||||
# ================================================
|
||||
|
||||
/aio/content/guide/npm-packages.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/browser-support.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/typescript-configuration.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/setup-systemjs-anatomy.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/setup.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/setup/** @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/deployment.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/releases.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/updating.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Docs: marketing
|
||||
# ================================================
|
||||
|
||||
/aio/content/marketing/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/marketing/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/navigation.json @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/license.md @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Public API
|
||||
# ================================================
|
||||
|
||||
/tools/public_api_guard/** @angular/fw-public-api
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Build & CI Owners
|
||||
# ================================================
|
||||
|
||||
/* @angular/fw-dev-infra
|
||||
/.buildkite/** @angular/fw-dev-infra
|
||||
/.circleci/** @angular/fw-dev-infra
|
||||
/.github/** @angular/fw-dev-infra
|
||||
/docs/BAZEL.md @angular/fw-dev-infra
|
||||
/scripts/** @angular/fw-dev-infra
|
||||
/third_party/** @angular/fw-dev-infra
|
||||
/tools/** @angular/fw-dev-infra
|
||||
*.bzl @angular/fw-dev-infra
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# CODEOWNERS Owners owners ...
|
||||
# ================================================
|
||||
|
||||
/.github/CODEOWNERS @IgorMinar @angular/framework-global-approvers
|
3
.github/angular-robot.yml
vendored
3
.github/angular-robot.yml
vendored
@ -81,7 +81,7 @@ merge:
|
||||
# require that the PR has reviews from all requested reviewers
|
||||
#
|
||||
# This enables us to request reviews from both eng and tech writers, or multiple eng folks, and prevents accidental merges.
|
||||
# Rather than merging PRs with pending reviews, if all PullApprove requirements are satisfied and additional reviews are not needed pending reviewers should be removed via GitHub UI (this also leaves an audit trail behind these decisions).
|
||||
# Rather than merging PRs with pending reviews, if all approvals are obtained and additional reviews are not needed, any pending reviewers should be removed via GitHub UI (this also leaves an audit trail behind these decisions).
|
||||
requireReviews: true,
|
||||
|
||||
# whether the PR shouldn't have a conflict with the base branch
|
||||
@ -102,7 +102,6 @@ merge:
|
||||
# list of PR statuses that need to be successful
|
||||
requiredStatuses:
|
||||
- "continuous-integration/travis-ci/pr"
|
||||
- "code-review/pullapprove"
|
||||
- "ci/circleci: build"
|
||||
- "ci/circleci: lint"
|
||||
|
||||
|
541
.pullapprove.yml
541
.pullapprove.yml
@ -1,541 +0,0 @@
|
||||
# Configuration for pullapprove.com
|
||||
#
|
||||
# Approval access and primary role is determined by info in the project ownership spreadsheet:
|
||||
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
|
||||
#
|
||||
# === GitHub username to Full name map ===
|
||||
#
|
||||
# alexeagle - Alex Eagle
|
||||
# alxhub - Alex Rickabaugh
|
||||
# andrewseguin - Andrew Seguin
|
||||
# benlesh - Ben Lesh
|
||||
# brandonroberts - Brandon Roberts
|
||||
# brocco - Mike Brocchi
|
||||
# filipesilva - Filipe Silva
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
# IgorMinar - Igor Minar
|
||||
# jasonaden - Jason Aden
|
||||
# jenniferfell - Jennifer Fell
|
||||
# kara - Kara Erickson
|
||||
# kyliau - Keen Yee Liau
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
# pkozlowski-opensource - Pawel Kozlowski
|
||||
# robwormald - Rob Wormald
|
||||
# vikerman - Vikram Subramanian
|
||||
|
||||
|
||||
version: 2
|
||||
|
||||
group_defaults:
|
||||
required: 1
|
||||
reset_on_reopened:
|
||||
enabled: true
|
||||
approve_by_comment:
|
||||
enabled: false
|
||||
# see http://docs.pullapprove.com/groups/author_approval/
|
||||
author_approval:
|
||||
# If the author is a reviewer on the PR, they will automatically have an "approved" status.
|
||||
auto: true
|
||||
|
||||
groups:
|
||||
# Require all PRs to have at least one approval from *someone*
|
||||
all:
|
||||
users: all
|
||||
required: 1
|
||||
rejection_value: -999
|
||||
# In this group, your self-approval does not count
|
||||
author_approval:
|
||||
auto: false
|
||||
ignored: true
|
||||
files:
|
||||
include:
|
||||
- "*"
|
||||
|
||||
root:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "*"
|
||||
exclude:
|
||||
- "WORKSPACE"
|
||||
- "BUILD.bazel"
|
||||
- ".circleci/*"
|
||||
- "aio/*"
|
||||
- "integration/*"
|
||||
- "modules/*"
|
||||
- "packages/*"
|
||||
- "tools/*"
|
||||
users:
|
||||
- alexeagle
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
|
||||
public-api:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "tools/public_api_guard/*"
|
||||
users:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
|
||||
bazel:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "WORKSPACE"
|
||||
- ".bazel*"
|
||||
- "*.bazel"
|
||||
- "*.bzl"
|
||||
- "packages/bazel/*"
|
||||
- "/docs/BAZEL.md"
|
||||
users:
|
||||
- alexeagle #primary
|
||||
- kyliau
|
||||
- IgorMinar #fallback
|
||||
- mhevery
|
||||
- vikerman #fallback
|
||||
- kara
|
||||
|
||||
build-and-ci:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "*.yml"
|
||||
- "*.json"
|
||||
- "*.lock"
|
||||
- "tools/*"
|
||||
exclude:
|
||||
- "aio/*"
|
||||
- "packages/core/test/bundling/*"
|
||||
- "tools/public_api_guard/*"
|
||||
users:
|
||||
- IgorMinar #primary
|
||||
- alexeagle
|
||||
- jasonaden
|
||||
- mhevery #fallback
|
||||
|
||||
integration:
|
||||
conditions:
|
||||
files:
|
||||
- "integration/*"
|
||||
users:
|
||||
- alexeagle
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
||||
core:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/core/*"
|
||||
- "aio/content/guide/bootstrapping.md"
|
||||
- "aio/content/examples/bootstrapping/*"
|
||||
- "aio/content/guide/attribute-directives.md"
|
||||
- "aio/content/examples/attribute-directives/*"
|
||||
- "aio/content/images/guide/attribute-directives/*"
|
||||
- "aio/content/guide/structural-directives.md"
|
||||
- "aio/content/examples/structural-directives/*"
|
||||
- "aio/content/images/guide/structural-directives/*"
|
||||
- "aio/content/guide/dynamic-component-loader.md"
|
||||
- "aio/content/examples/dynamic-component-loader/*"
|
||||
- "aio/content/images/guide/dynamic-component-loader/*"
|
||||
- "aio/content/guide/template-syntax.md"
|
||||
- "aio/content/examples/template-syntax/*"
|
||||
- "aio/content/images/guide/template-syntax/*"
|
||||
- "aio/content/guide/dependency-injection.md"
|
||||
- "aio/content/examples/dependency-injection/*"
|
||||
- "aio/content/images/guide/dependency-injection/*"
|
||||
- "aio/content/guide/dependency-injection-in-action.md"
|
||||
- "aio/content/examples/dependency-injection-in-action/*"
|
||||
- "aio/content/images/guide/dependency-injection-in-action/*"
|
||||
- "aio/content/guide/hierarchical-dependency-injection.md"
|
||||
- "aio/content/examples/hierarchical-dependency-injection/*"
|
||||
- "aio/content/guide/singleton-services.md"
|
||||
- "aio/content/guide/dependency-injection-pattern.md"
|
||||
- "aio/content/guide/providers.md"
|
||||
- "aio/content/examples/providers/*"
|
||||
- "aio/content/guide/component-interaction.md"
|
||||
- "aio/content/examples/component-interaction/*"
|
||||
- "aio/content/images/guide/component-interaction/*"
|
||||
- "aio/content/guide/component-styles.md"
|
||||
- "aio/content/examples/component-styles/*"
|
||||
- "aio/content/guide/lifecycle-hooks.md"
|
||||
- "aio/content/examples/lifecycle-hooks/*"
|
||||
- "aio/content/images/guide/lifecycle-hooks/*"
|
||||
- "aio/content/examples/ngcontainer/*"
|
||||
- "aio/content/images/guide/ngcontainer/*"
|
||||
- "aio/content/guide/pipes.md"
|
||||
- "aio/content/examples/pipes/*"
|
||||
- "aio/content/images/guide/pipes/*"
|
||||
- "aio/content/guide/entry-components.md"
|
||||
- "aio/content/guide/set-document-title.md"
|
||||
- "aio/content/examples/set-document-title/*"
|
||||
- "aio/content/images/guide/set-document-title/*"
|
||||
- "aio/content/guide/ngmodules.md"
|
||||
- "aio/content/examples/ngmodules/*"
|
||||
- "aio/content/examples/ngmodule/*"
|
||||
- "aio/content/images/guide/ngmodule/*"
|
||||
- "aio/content/guide/ngmodule-faq.md"
|
||||
- "aio/content/examples/ngmodule-faq/*"
|
||||
- "aio/content/guide/module-types.md"
|
||||
- "aio/content/guide/sharing-ngmodules.md"
|
||||
- "aio/content/guide/frequent-ngmodules.md"
|
||||
- "aio/content/images/guide/frequent-ngmodules/*"
|
||||
- "aio/content/guide/ngmodule-api.md"
|
||||
- "aio/content/guide/ngmodule-vs-jsmodule.md"
|
||||
- "aio/content/guide/feature-modules.md"
|
||||
- "aio/content/examples/feature-modules/*"
|
||||
- "aio/content/images/guide/feature-modules/*"
|
||||
- "aio/content/guide/lazy-loading-ngmodules.md"
|
||||
- "aio/content/examples/lazy-loading-ngmodules/*"
|
||||
- "aio/content/images/guide/lazy-loading-ngmodules"
|
||||
users:
|
||||
- mhevery #primary
|
||||
- jasonaden
|
||||
- kara
|
||||
- IgorMinar
|
||||
- jenniferfell #docs only
|
||||
|
||||
animations:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/animations/*"
|
||||
- "packages/platform-browser/animations/*"
|
||||
- "aio/content/guide/animations.md"
|
||||
- "aio/content/examples/animations/*"
|
||||
- "aio/content/images/guide/animations/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
- mhevery #fallback
|
||||
- IgorMinar #fallback
|
||||
- jenniferfell #docs only
|
||||
- kara
|
||||
|
||||
compiler/i18n:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/compiler/src/i18n/*"
|
||||
- "aio/content/guide/i18n.md"
|
||||
- "aio/content/examples/i18n/*"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
- kara
|
||||
|
||||
compiler:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/compiler/*"
|
||||
- "aio/content/guide/aot-compiler.md"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
- jenniferfell #docs only
|
||||
- kara
|
||||
|
||||
compiler-cli/ngtools:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/compiler-cli/src/ngtools*"
|
||||
users:
|
||||
- hansl
|
||||
- filipesilva #fallback
|
||||
- IgorMinar #fallback
|
||||
- kara
|
||||
|
||||
compiler-cli:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "packages/compiler-cli/*"
|
||||
exclude:
|
||||
- "packages/compiler-cli/src/ngtools*"
|
||||
users:
|
||||
- alexeagle
|
||||
- alxhub
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- kara
|
||||
|
||||
common:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "packages/common/*"
|
||||
exclude:
|
||||
- "packages/common/http/*"
|
||||
users:
|
||||
- pkozlowski-opensource #primary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- kara
|
||||
|
||||
forms:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/forms/*"
|
||||
- "aio/content/guide/forms.md"
|
||||
- "aio/content/examples/forms/*"
|
||||
- "aio/content/images/guide/forms/*"
|
||||
- "aio/content/guide/forms-overview.md"
|
||||
- "aio/content/examples/forms-overview/*"
|
||||
- "aio/content/images/guide/forms-overview/*"
|
||||
- "aio/content/guide/form-validation.md"
|
||||
- "aio/content/examples/form-validation/*"
|
||||
- "aio/content/images/guide/form-validation/*"
|
||||
- "aio/content/guide/dynamic-form.md"
|
||||
- "aio/content/examples/dynamic-form/*"
|
||||
- "aio/content/images/guide/dynamic-form/*"
|
||||
- "aio/content/guide/reactive-forms.md"
|
||||
- "aio/content/examples/reactive-forms/*"
|
||||
- "aio/content/images/guide/reactive-forms/*"
|
||||
users:
|
||||
- kara #primary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
|
||||
http:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/common/http/*"
|
||||
- "packages/http/*"
|
||||
- "aio/content/guide/http.md"
|
||||
- "aio/content/examples/http/*"
|
||||
- "aio/content/images/guide/http/*"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- IgorMinar
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
|
||||
language-service:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/language-service/*"
|
||||
- "aio/content/guide/language-service.md"
|
||||
- "aio/content/images/guide/language-service/*"
|
||||
users:
|
||||
- kyliau #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
|
||||
router:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/router/*"
|
||||
- "aio/content/guide/router.md"
|
||||
- "aio/content/examples/router/*"
|
||||
- "aio/content/images/guide/router/*"
|
||||
users:
|
||||
- jasonaden #primary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
- kara
|
||||
|
||||
testing:
|
||||
conditions:
|
||||
files:
|
||||
- "*/testing/*"
|
||||
- "aio/content/guide/testing.md"
|
||||
- "aio/content/examples/testing/*"
|
||||
- "aio/content/images/guide/testing/*"
|
||||
users:
|
||||
- vikerman
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
|
||||
upgrade:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/upgrade/*"
|
||||
- "aio/content/guide/upgrade.md"
|
||||
- "aio/content/examples/upgrade-module/*"
|
||||
- "aio/content/images/guide/upgrade/*"
|
||||
- "aio/content/examples/upgrade-phonecat-1-typescript/*"
|
||||
- "aio/content/examples/upgrade-phonecat-2-hybrid/*"
|
||||
- "aio/content/examples/upgrade-phonecat-3-final/*"
|
||||
- "aio/content/guide/upgrade-performance.md"
|
||||
- "aio/content/guide/ajs-quick-reference.md"
|
||||
- "aio/content/examples/ajs-quick-reference/*"
|
||||
users:
|
||||
- petebacondarwin #primary
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
- kara
|
||||
|
||||
platform-browser:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/platform-browser/*"
|
||||
users:
|
||||
- mhevery #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- kara
|
||||
|
||||
platform-server:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/platform-server/*"
|
||||
- "aio/content/guide/universal.md"
|
||||
- "aio/content/examples/universal/*"
|
||||
users:
|
||||
- vikerman #primary
|
||||
- alxhub #secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
|
||||
platform-webworker:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/platform-webworker/*"
|
||||
users:
|
||||
- mhevery #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- kara
|
||||
|
||||
service-worker:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/service-worker/*"
|
||||
- "aio/content/guide/service-worker-getting-started.md"
|
||||
- "aio/content/examples/service-worker-getting-started/*"
|
||||
- "aio/content/guide/service-worker-communications.md"
|
||||
- "aio/content/guide/service-worker-config.md"
|
||||
- "aio/content/guide/service-worker-devops.md"
|
||||
- "aio/content/guide/service-worker-intro.md"
|
||||
- "aio/content/images/guide/service-worker/*"
|
||||
users:
|
||||
- gkalpak #primary
|
||||
- alxhub
|
||||
- IgorMinar
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
|
||||
elements:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/elements/*"
|
||||
- "aio/content/examples/elements/*"
|
||||
- "aio/content/images/guide/elements/*"
|
||||
- "aio/content/guide/elements.md"
|
||||
users:
|
||||
- andrewseguin #primary
|
||||
- gkalpak
|
||||
- robwormald
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
||||
- kara
|
||||
|
||||
benchpress:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/benchpress/*"
|
||||
users:
|
||||
- alxhub # primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
docs-infra:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "aio/*"
|
||||
exclude:
|
||||
- "aio/content/*"
|
||||
users:
|
||||
- petebacondarwin #primary
|
||||
- IgorMinar
|
||||
- gkalpak
|
||||
- mhevery #fallback
|
||||
|
||||
docs/guide-and-tutorial:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "aio/content/*"
|
||||
exclude:
|
||||
- "aio/content/marketing/*"
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
users:
|
||||
- stephenfluin
|
||||
- jenniferfell
|
||||
- brandonroberts
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar
|
||||
- mhevery #fallback
|
||||
|
||||
docs/marketing:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "aio/content/marketing/*"
|
||||
- "aio/content/images/marketing/*"
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
users:
|
||||
- stephenfluin
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar
|
||||
- robwormald
|
||||
- mhevery #fallback
|
||||
|
||||
docs/observables:
|
||||
conditions:
|
||||
files:
|
||||
- "aio/content/examples/observables/*"
|
||||
- "aio/content/images/guide/observables/*"
|
||||
- "aio/content/guide/observables.md"
|
||||
- "aio/content/guide/comparing-observables.md"
|
||||
- "aio/content/examples/observables-in-angular/*"
|
||||
- "aio/content/images/guide/observables-in-angular/*"
|
||||
- "aio/content/guide/observables-in-angular.md"
|
||||
- "aio/content/examples/practical-observable-usage/*"
|
||||
- "aio/content/guide/practical-observable-usage.md"
|
||||
- "aio/content/examples/rx-library/*"
|
||||
- "aio/content/guide/rx-library.md"
|
||||
users:
|
||||
- jasonaden
|
||||
- benlesh
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
- jenniferfell #docs only
|
||||
|
||||
docs/packaging:
|
||||
conditions:
|
||||
files:
|
||||
- "aio/content/guide/npm-packages.md"
|
||||
- "aio/content/guide/browser-support.md"
|
||||
- "aio/content/guide/typescript-configuration.md"
|
||||
- "aio/content/guide/setup-systemjs-anatomy.md"
|
||||
- "aio/content/examples/setup/*"
|
||||
- "aio/content/guide/setup.md"
|
||||
- "aio/content/guide/deployment.md"
|
||||
- "aio/content/guide/releases.md"
|
||||
- "aio/content/guide/updating.md"
|
||||
users:
|
||||
- IgorMinar #primary
|
||||
- alexeagle
|
||||
- hansl
|
||||
- mhevery #fallback
|
||||
- jenniferfell #docs only
|
@ -74,7 +74,10 @@ Command syntax is shown as follows:
|
||||
* Option names are prefixed with a double dash (--).
|
||||
Option aliases are prefixed with a single dash (-).
|
||||
Arguments are not prefixed.
|
||||
For example: `ng build my-app -c production`
|
||||
For example:
|
||||
<code-example format="." language="bash">
|
||||
ng build my-app -c production
|
||||
</code-example>
|
||||
|
||||
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option.
|
||||
|
||||
|
@ -8,7 +8,7 @@ Someone with committer access will do the rest.
|
||||
|
||||
# Change approvals
|
||||
|
||||
Change approvals in our monorepo are managed via [pullapprove.com](https://about.pullapprove.com/) and are configured via the `.pullapprove.yaml` file.
|
||||
Change approvals in our monorepo are managed via [GitHub CODEOWNERS](https://help.github.com/articles/about-codeowners/) and are configured via the `.github/CODEOWNERS` file.
|
||||
|
||||
|
||||
# Merging
|
||||
|
@ -145,7 +145,7 @@ If a PR is missing the "PR target: *" label, or if the label is set to "TBD" whe
|
||||
|
||||
Before a PR can be merged it must be approved by the appropriate reviewer(s).
|
||||
|
||||
To ensure that there right people review each change, we configured [PullApprove bot](https://about.pullapprove.com/) via (`.pullapprove.yaml`) to provide aggregate approval state via the GitHub PR Status API.
|
||||
To ensure that the right people review each change, we configured [GitHub CODEOWNERS](https://help.github.com/articles/about-codeowners/) (via `.github/CODEOWNERS`) and require that each PR has at least one approval from the appropriate code owner.
|
||||
|
||||
Note that approved state does not mean a PR is ready to be merged.
|
||||
For example, a reviewer might approve the PR but request a minor tweak that doesn't need further review, e.g., a rebase or small uncontroversial change.
|
||||
|
135
integration/bazel-schematics/angular.json.original
Normal file
135
integration/bazel-schematics/angular.json.original
Normal file
@ -0,0 +1,135 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"demo": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/demo",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "demo:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"demo-e2e": {
|
||||
"root": "e2e/",
|
||||
"projectType": "application",
|
||||
"prefix": "",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "demo:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "demo:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": "e2e/tsconfig.e2e.json",
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "demo"
|
||||
}
|
20
integration/bazel-schematics/app.e2e-spec.ts
Normal file
20
integration/bazel-schematics/app.e2e-spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Welcome to demo!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const logs = await browser.manage().logs().get('browser');
|
||||
expect(logs).toEqual([]);
|
||||
});
|
||||
});
|
14
integration/bazel-schematics/index.html
Normal file
14
integration/bazel-schematics/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Demo</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
@ -2,7 +2,7 @@
|
||||
|
||||
set -eux -o pipefail
|
||||
|
||||
function test() {
|
||||
function testBazel() {
|
||||
# Set up
|
||||
bazel version
|
||||
rm -rf demo
|
||||
@ -18,4 +18,20 @@ function test() {
|
||||
ng e2e
|
||||
}
|
||||
|
||||
test
|
||||
function testNonBazel() {
|
||||
# Replace angular.json that uses Bazel builder with the default generated by CLI
|
||||
cp ../angular.json.original ./angular.json
|
||||
# TODO(kyliau) Remove this once the additional assertion is added to CLI
|
||||
cp ../app.e2e-spec.ts ./e2e/src/
|
||||
# TODO(kyliau) Remove this once web_package rule is in use
|
||||
cp ../index.html ./src/
|
||||
rm -rf dist src/main.dev.ts src/main.prod.ts
|
||||
# Just make a symlink instead of full yarn install to expose node_modules
|
||||
ln -s $(bazel info output_base)/external/npm/node_modules node_modules
|
||||
ng build
|
||||
ng test --watch=false
|
||||
ng e2e
|
||||
}
|
||||
|
||||
testBazel
|
||||
testNonBazel
|
||||
|
@ -65,7 +65,7 @@
|
||||
"fs-extra": "4.0.2",
|
||||
"jasmine": "^3.1.0",
|
||||
"jasmine-core": "^3.1.0",
|
||||
"karma": "^2.0.4",
|
||||
"karma": "^3.1.4",
|
||||
"magic-string": "^0.25.0",
|
||||
"minimist": "1.2.0",
|
||||
"mock-fs": "^4.5.0",
|
||||
@ -110,7 +110,7 @@
|
||||
"firefox-profile": "1.0.3",
|
||||
"glob": "7.1.2",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-clang-format": "1.0.27",
|
||||
"gulp-clang-format": "1.0.23",
|
||||
"gulp-connect": "5.0.0",
|
||||
"gulp-conventional-changelog": "^2.0.3",
|
||||
"gulp-filter": "^5.1.0",
|
||||
|
@ -15,6 +15,7 @@ ts_library(
|
||||
"//packages/platform-browser",
|
||||
"//packages/platform-browser-dynamic",
|
||||
"//packages/platform-browser/testing",
|
||||
"//packages/private/testing",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_out
|
||||
import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
|
||||
import {TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {modifiedInIvy} from '@angular/private/testing';
|
||||
|
||||
describe('insert/remove', () => {
|
||||
|
||||
@ -106,17 +107,20 @@ describe('insert/remove', () => {
|
||||
|
||||
}));
|
||||
|
||||
it('should resolve a with injector', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.componentInstance.cmpRef = null;
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
fixture.detectChanges();
|
||||
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
|
||||
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||
expect(cmpRef.instance.testToken).toBeNull();
|
||||
}));
|
||||
modifiedInIvy('Static ViewChild and ContentChild queries are resolved in update mode')
|
||||
.it('should resolve with an injector', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
// We are accessing a ViewChild (ngComponentOutlet) before change detection has run
|
||||
fixture.componentInstance.cmpRef = null;
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
fixture.detectChanges();
|
||||
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
|
||||
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||
expect(cmpRef.instance.testToken).toBeNull();
|
||||
}));
|
||||
|
||||
it('should render projectable nodes, if supplied', async(() => {
|
||||
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
|
||||
|
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ReferencesRegistry} from '../../../ngtsc/annotations';
|
||||
import {Declaration} from '../../../ngtsc/host';
|
||||
import {ResolvedReference} from '../../../ngtsc/metadata';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {isDefined} from '../utils';
|
||||
|
||||
export interface ModuleWithProvidersInfo {
|
||||
/**
|
||||
* The declaration (in the .d.ts file) of the function that returns
|
||||
* a `ModuleWithProviders object, but has a signature that needs
|
||||
* a type parameter adding.
|
||||
*/
|
||||
declaration: ts.MethodDeclaration|ts.FunctionDeclaration;
|
||||
/**
|
||||
* The NgModule class declaration (in the .d.ts file) to add as a type parameter.
|
||||
*/
|
||||
ngModule: Declaration;
|
||||
}
|
||||
|
||||
export type ModuleWithProvidersAnalyses = Map<ts.SourceFile, ModuleWithProvidersInfo[]>;
|
||||
export const ModuleWithProvidersAnalyses = Map;
|
||||
|
||||
export class ModuleWithProvidersAnalyzer {
|
||||
constructor(private host: NgccReflectionHost, private referencesRegistry: ReferencesRegistry) {}
|
||||
|
||||
analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses {
|
||||
const analyses = new ModuleWithProvidersAnalyses();
|
||||
const rootFiles = this.getRootFiles(program);
|
||||
rootFiles.forEach(f => {
|
||||
const fns = this.host.getModuleWithProvidersFunctions(f);
|
||||
fns && fns.forEach(fn => {
|
||||
const dtsFn = this.getDtsDeclaration(fn.declaration);
|
||||
const typeParam = dtsFn.type && ts.isTypeReferenceNode(dtsFn.type) &&
|
||||
dtsFn.type.typeArguments && dtsFn.type.typeArguments[0] ||
|
||||
null;
|
||||
if (!typeParam || isAnyKeyword(typeParam)) {
|
||||
// Either we do not have a parameterized type or the type is `any`.
|
||||
let ngModule = this.host.getDeclarationOfIdentifier(fn.ngModule);
|
||||
if (!ngModule) {
|
||||
throw new Error(
|
||||
`Cannot find a declaration for NgModule ${fn.ngModule.text} referenced in ${fn.declaration.getText()}`);
|
||||
}
|
||||
// For internal (non-library) module references, redirect the module's value declaration
|
||||
// to its type declaration.
|
||||
if (ngModule.viaModule === null) {
|
||||
const dtsNgModule = this.host.getDtsDeclaration(ngModule.node);
|
||||
if (!dtsNgModule) {
|
||||
throw new Error(
|
||||
`No typings declaration can be found for the referenced NgModule class in ${fn.declaration.getText()}.`);
|
||||
}
|
||||
if (!ts.isClassDeclaration(dtsNgModule)) {
|
||||
throw new Error(
|
||||
`The referenced NgModule in ${fn.declaration.getText()} is not a class declaration in the typings program; instead we get ${dtsNgModule.getText()}`);
|
||||
}
|
||||
// Record the usage of the internal module as it needs to become an exported symbol
|
||||
this.referencesRegistry.add(new ResolvedReference(ngModule.node, fn.ngModule));
|
||||
|
||||
ngModule = {node: dtsNgModule, viaModule: null};
|
||||
}
|
||||
const dtsFile = dtsFn.getSourceFile();
|
||||
const analysis = analyses.get(dtsFile) || [];
|
||||
analysis.push({declaration: dtsFn, ngModule});
|
||||
analyses.set(dtsFile, analysis);
|
||||
}
|
||||
});
|
||||
});
|
||||
return analyses;
|
||||
}
|
||||
|
||||
private getRootFiles(program: ts.Program): ts.SourceFile[] {
|
||||
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
|
||||
}
|
||||
|
||||
private getDtsDeclaration(fn: ts.SignatureDeclaration) {
|
||||
let dtsFn: ts.Declaration|null = null;
|
||||
const containerClass = this.host.getClassSymbol(fn.parent);
|
||||
const fnName = fn.name && ts.isIdentifier(fn.name) && fn.name.text;
|
||||
if (containerClass && fnName) {
|
||||
const dtsClass = this.host.getDtsDeclaration(containerClass.valueDeclaration);
|
||||
// Get the declaration of the matching static method
|
||||
dtsFn = dtsClass && ts.isClassDeclaration(dtsClass) ?
|
||||
dtsClass.members
|
||||
.find(
|
||||
member => ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) &&
|
||||
member.name.text === fnName) as ts.Declaration :
|
||||
null;
|
||||
} else {
|
||||
dtsFn = this.host.getDtsDeclaration(fn);
|
||||
}
|
||||
if (!dtsFn) {
|
||||
throw new Error(`Matching type declaration for ${fn.getText()} is missing`);
|
||||
}
|
||||
if (!isFunctionOrMethod(dtsFn)) {
|
||||
throw new Error(
|
||||
`Matching type declaration for ${fn.getText()} is not a function: ${dtsFn.getText()}`);
|
||||
}
|
||||
return dtsFn;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function isFunctionOrMethod(declaration: ts.Declaration): declaration is ts.FunctionDeclaration|
|
||||
ts.MethodDeclaration {
|
||||
return ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration);
|
||||
}
|
||||
|
||||
function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode {
|
||||
return typeParam.kind === ts.SyntaxKind.AnyKeyword;
|
||||
}
|
@ -15,7 +15,7 @@ import {hasNameIdentifier, isDefined} from '../utils';
|
||||
export interface ExportInfo {
|
||||
identifier: string;
|
||||
from: string;
|
||||
dtsFrom: string|null;
|
||||
dtsFrom?: string|null;
|
||||
}
|
||||
export type PrivateDeclarationsAnalyses = ExportInfo[];
|
||||
|
||||
@ -52,7 +52,7 @@ export class PrivateDeclarationsAnalyzer {
|
||||
return Array.from(privateDeclarations.keys()).map(id => {
|
||||
const from = id.getSourceFile().fileName;
|
||||
const declaration = privateDeclarations.get(id) !;
|
||||
const dtsDeclaration = this.host.getDtsDeclarationOfClass(declaration.node);
|
||||
const dtsDeclaration = this.host.getDtsDeclaration(declaration.node);
|
||||
const dtsFrom = dtsDeclaration && dtsDeclaration.getSourceFile().fileName;
|
||||
return {identifier: id.text, from, dtsFrom};
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ import {BundleProgram} from '../packages/bundle_program';
|
||||
import {findAll, getNameText, isDefined} from '../utils';
|
||||
|
||||
import {DecoratedClass} from './decorated_class';
|
||||
import {NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||
|
||||
export const DECORATORS = 'decorators' as ts.__String;
|
||||
export const PROP_DECORATORS = 'propDecorators' as ts.__String;
|
||||
@ -49,10 +49,10 @@ export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String;
|
||||
* a static method called `ctorParameters`.
|
||||
*/
|
||||
export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost {
|
||||
protected dtsClassMap: Map<string, ts.ClassDeclaration>|null;
|
||||
protected dtsDeclarationMap: Map<string, ts.Declaration>|null;
|
||||
constructor(protected isCore: boolean, checker: ts.TypeChecker, dts?: BundleProgram|null) {
|
||||
super(checker);
|
||||
this.dtsClassMap = dts && this.computeDtsClassMap(dts.path, dts.program) || null;
|
||||
this.dtsDeclarationMap = dts && this.computeDtsDeclarationMap(dts.path, dts.program) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -327,15 +327,15 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* is not a class or has an unknown number of type parameters.
|
||||
*/
|
||||
getGenericArityOfClass(clazz: ts.Declaration): number|null {
|
||||
const dtsClass = this.getDtsDeclarationOfClass(clazz);
|
||||
if (dtsClass) {
|
||||
return dtsClass.typeParameters ? dtsClass.typeParameters.length : 0;
|
||||
const dtsDeclaration = this.getDtsDeclaration(clazz);
|
||||
if (dtsDeclaration && ts.isClassDeclaration(dtsDeclaration)) {
|
||||
return dtsDeclaration.typeParameters ? dtsDeclaration.typeParameters.length : 0;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an exported declaration of a class (maybe downleveled to a variable) and look up the
|
||||
* Take an exported declaration of a class (maybe down-leveled to a variable) and look up the
|
||||
* declaration of its type in a separate .d.ts tree.
|
||||
*
|
||||
* This function is allowed to return `null` if the current compilation unit does not have a
|
||||
@ -346,19 +346,47 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* Note that the `ts.ClassDeclaration` returned from this function may not be from the same
|
||||
* `ts.Program` as the input declaration.
|
||||
*/
|
||||
getDtsDeclarationOfClass(declaration: ts.Declaration): ts.ClassDeclaration|null {
|
||||
if (this.dtsClassMap) {
|
||||
if (ts.isClassDeclaration(declaration)) {
|
||||
if (!declaration.name || !ts.isIdentifier(declaration.name)) {
|
||||
throw new Error(
|
||||
`Cannot get the dts file for a class declaration that has no indetifier: ${declaration.getText()} in ${declaration.getSourceFile().fileName}`);
|
||||
}
|
||||
return this.dtsClassMap.get(declaration.name.text) || null;
|
||||
}
|
||||
getDtsDeclaration(declaration: ts.Declaration): ts.Declaration|null {
|
||||
if (!this.dtsDeclarationMap) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
if (!isNamedDeclaration(declaration)) {
|
||||
throw new Error(
|
||||
`Cannot get the dts file for a declaration that has no name: ${declaration.getText()} in ${declaration.getSourceFile().fileName}`);
|
||||
}
|
||||
return this.dtsDeclarationMap.get(declaration.name.text) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the given source file for exported functions and static class methods that return
|
||||
* ModuleWithProviders objects.
|
||||
* @param f The source file to search for these functions
|
||||
* @returns An array of function declarations that look like they return ModuleWithProviders
|
||||
* objects.
|
||||
*/
|
||||
getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[] {
|
||||
const exports = this.getExportsOfModule(f);
|
||||
if (!exports) return [];
|
||||
const infos: ModuleWithProvidersFunction[] = [];
|
||||
exports.forEach((declaration, name) => {
|
||||
if (this.isClass(declaration.node)) {
|
||||
this.getMembersOfClass(declaration.node).forEach(member => {
|
||||
if (member.isStatic) {
|
||||
const info = this.parseForModuleWithProviders(member.node);
|
||||
if (info) {
|
||||
infos.push(info);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const info = this.parseForModuleWithProviders(declaration.node);
|
||||
if (info) {
|
||||
infos.push(info);
|
||||
}
|
||||
}
|
||||
});
|
||||
return infos;
|
||||
}
|
||||
|
||||
///////////// Protected Helpers /////////////
|
||||
|
||||
@ -738,7 +766,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
if (isNamedDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
|
||||
if (isNamedDeclaration(node)) {
|
||||
name = node.name.text;
|
||||
nameNode = node.name;
|
||||
} else {
|
||||
@ -846,8 +874,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameter type and decorators for a class where the information is stored on
|
||||
* in calls to `__decorate` helpers.
|
||||
* Get the parameter type and decorators for a class where the information is stored via
|
||||
* calls to `__decorate` helpers.
|
||||
*
|
||||
* Reflect over the helpers to find the decorators and types about each of
|
||||
* the class's constructor parameters.
|
||||
@ -1002,9 +1030,9 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* @param dtsProgram The program containing all the typings files.
|
||||
* @returns a map of class names to class declarations.
|
||||
*/
|
||||
protected computeDtsClassMap(dtsRootFileName: string, dtsProgram: ts.Program):
|
||||
Map<string, ts.ClassDeclaration> {
|
||||
const dtsClassMap = new Map<string, ts.ClassDeclaration>();
|
||||
protected computeDtsDeclarationMap(dtsRootFileName: string, dtsProgram: ts.Program):
|
||||
Map<string, ts.Declaration> {
|
||||
const dtsDeclarationMap = new Map<string, ts.Declaration>();
|
||||
const checker = dtsProgram.getTypeChecker();
|
||||
|
||||
// First add all the classes that are publicly exported from the entry-point
|
||||
@ -1012,13 +1040,38 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
if (!rootFile) {
|
||||
throw new Error(`The given file ${dtsRootFileName} is not part of the typings program.`);
|
||||
}
|
||||
collectExportedClasses(checker, dtsClassMap, rootFile);
|
||||
collectExportedDeclarations(checker, dtsDeclarationMap, rootFile);
|
||||
|
||||
// Now add any additional classes that are exported from individual dts files,
|
||||
// but are not publicly exported from the entry-point.
|
||||
dtsProgram.getSourceFiles().forEach(
|
||||
sourceFile => { collectExportedClasses(checker, dtsClassMap, sourceFile); });
|
||||
return dtsClassMap;
|
||||
sourceFile => { collectExportedDeclarations(checker, dtsDeclarationMap, sourceFile); });
|
||||
return dtsDeclarationMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given node, to see if it is a function that returns a `ModuleWithProviders` object.
|
||||
* @param node a node to check to see if it is a function that returns a `ModuleWithProviders`
|
||||
* object.
|
||||
* @returns info about the function if it does return a `ModuleWithProviders` object; `null`
|
||||
* otherwise.
|
||||
*/
|
||||
protected parseForModuleWithProviders(node: ts.Node|null): ModuleWithProvidersFunction|null {
|
||||
const declaration =
|
||||
node && (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) ? node : null;
|
||||
const body = declaration ? this.getDefinitionOfFunction(declaration).body : null;
|
||||
const lastStatement = body && body[body.length - 1];
|
||||
const returnExpression =
|
||||
lastStatement && ts.isReturnStatement(lastStatement) && lastStatement.expression || null;
|
||||
const ngModuleProperty = returnExpression && ts.isObjectLiteralExpression(returnExpression) &&
|
||||
returnExpression.properties.find(
|
||||
prop =>
|
||||
!!prop.name && ts.isIdentifier(prop.name) && prop.name.text === 'ngModule') ||
|
||||
null;
|
||||
const ngModule = ngModuleProperty && ts.isPropertyAssignment(ngModuleProperty) &&
|
||||
ts.isIdentifier(ngModuleProperty.initializer) && ngModuleProperty.initializer ||
|
||||
null;
|
||||
return ngModule && declaration && {ngModule, declaration};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1129,8 +1182,10 @@ function isThisAssignment(node: ts.Declaration): node is ts.BinaryExpression&
|
||||
node.left.expression.kind === ts.SyntaxKind.ThisKeyword;
|
||||
}
|
||||
|
||||
function isNamedDeclaration(node: ts.Declaration): node is ts.NamedDeclaration {
|
||||
return !!(node as any).name;
|
||||
function isNamedDeclaration(node: ts.Declaration): node is ts.NamedDeclaration&
|
||||
{name: ts.Identifier} {
|
||||
const anyNode: any = node;
|
||||
return !!anyNode.name && ts.isIdentifier(anyNode.name);
|
||||
}
|
||||
|
||||
|
||||
@ -1153,13 +1208,11 @@ function getFarLeftIdentifier(propertyAccess: ts.PropertyAccessExpression): ts.I
|
||||
}
|
||||
|
||||
/**
|
||||
* Search a source file for exported classes, storing them in the provided `dtsClassMap`.
|
||||
* @param checker The typechecker for the source program.
|
||||
* @param dtsClassMap The map in which to store the collected exported classes.
|
||||
* @param srcFile The source file to search for exported classes.
|
||||
* Collect mappings between exported declarations in a source file and its associated
|
||||
* declaration in the typings program.
|
||||
*/
|
||||
function collectExportedClasses(
|
||||
checker: ts.TypeChecker, dtsClassMap: Map<string, ts.ClassDeclaration>,
|
||||
function collectExportedDeclarations(
|
||||
checker: ts.TypeChecker, dtsDeclarationMap: Map<string, ts.Declaration>,
|
||||
srcFile: ts.SourceFile): void {
|
||||
const srcModule = srcFile && checker.getSymbolAtLocation(srcFile);
|
||||
const moduleExports = srcModule && checker.getExportsOfModule(srcModule);
|
||||
@ -1170,8 +1223,8 @@ function collectExportedClasses(
|
||||
}
|
||||
const declaration = exportedSymbol.valueDeclaration;
|
||||
const name = exportedSymbol.name;
|
||||
if (declaration && ts.isClassDeclaration(declaration) && !dtsClassMap.has(name)) {
|
||||
dtsClassMap.set(name, declaration);
|
||||
if (declaration && !dtsDeclarationMap.has(name)) {
|
||||
dtsDeclarationMap.set(name, declaration);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -19,6 +19,21 @@ export function isSwitchableVariableDeclaration(node: ts.Node):
|
||||
ts.isIdentifier(node.initializer) && node.initializer.text.endsWith(PRE_R3_MARKER);
|
||||
}
|
||||
|
||||
/**
|
||||
* A structure returned from `getModuleWithProviderInfo` that describes functions
|
||||
* that return ModuleWithProviders objects.
|
||||
*/
|
||||
export interface ModuleWithProvidersFunction {
|
||||
/**
|
||||
* The declaration of the function that returns the `ModuleWithProviders` object.
|
||||
*/
|
||||
declaration: ts.SignatureDeclaration;
|
||||
/**
|
||||
* The identifier of the `ngModule` property on the `ModuleWithProviders` object.
|
||||
*/
|
||||
ngModule: ts.Identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reflection host that has extra methods for looking at non-Typescript package formats
|
||||
*/
|
||||
@ -45,4 +60,13 @@ export interface NgccReflectionHost extends ReflectionHost {
|
||||
* @returns An array of decorated classes.
|
||||
*/
|
||||
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[];
|
||||
|
||||
/**
|
||||
* Search the given source file for exported functions and static class methods that return
|
||||
* ModuleWithProviders objects.
|
||||
* @param f The source file to search for these functions
|
||||
* @returns An array of info items about each of the functions that return ModuleWithProviders
|
||||
* objects.
|
||||
*/
|
||||
getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersFunction[];
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import {mkdir, mv} from 'shelljs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CompiledFile, DecorationAnalyzer} from '../analysis/decoration_analyzer';
|
||||
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analysis/module_with_providers_analyzer';
|
||||
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
|
||||
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
|
||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
||||
@ -25,6 +26,7 @@ import {EntryPoint} from './entry_point';
|
||||
import {EntryPointBundle} from './entry_point_bundle';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A Package is stored in a directory on disk and that directory can contain one or more package
|
||||
* formats - e.g. fesm2015, UMD, etc. Additionally, each package provides typings (`.d.ts` files).
|
||||
@ -59,13 +61,14 @@ export class Transformer {
|
||||
const reflectionHost = this.getHost(isCore, bundle);
|
||||
|
||||
// Parse and analyze the files.
|
||||
const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
this.analyzeProgram(reflectionHost, isCore, bundle);
|
||||
const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = this.analyzeProgram(reflectionHost, isCore, bundle);
|
||||
|
||||
// Transform the source files and source maps.
|
||||
const renderer = this.getRenderer(reflectionHost, isCore, bundle);
|
||||
const renderedFiles = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
// Write out all the transformed files.
|
||||
renderedFiles.forEach(file => this.writeFile(file));
|
||||
@ -102,16 +105,26 @@ export class Transformer {
|
||||
ProgramAnalyses {
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
|
||||
|
||||
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
|
||||
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
|
||||
|
||||
const decorationAnalyzer = new DecorationAnalyzer(
|
||||
typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore);
|
||||
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
|
||||
const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program);
|
||||
|
||||
const moduleWithProvidersAnalyzer =
|
||||
bundle.dts && new ModuleWithProvidersAnalyzer(reflectionHost, referencesRegistry);
|
||||
const moduleWithProvidersAnalyses = moduleWithProvidersAnalyzer &&
|
||||
moduleWithProvidersAnalyzer.analyzeProgram(bundle.src.program);
|
||||
|
||||
const privateDeclarationsAnalyzer =
|
||||
new PrivateDeclarationsAnalyzer(reflectionHost, referencesRegistry);
|
||||
const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program);
|
||||
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
|
||||
const privateDeclarationsAnalyses =
|
||||
privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program);
|
||||
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses};
|
||||
|
||||
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses};
|
||||
}
|
||||
|
||||
writeFile(file: FileInfo): void {
|
||||
@ -129,4 +142,5 @@ interface ProgramAnalyses {
|
||||
decorationAnalyses: Map<ts.SourceFile, CompiledFile>;
|
||||
switchMarkerAnalyses: SwitchMarkerAnalyses;
|
||||
privateDeclarationsAnalyses: ExportInfo[];
|
||||
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null;
|
||||
}
|
||||
|
@ -15,9 +15,10 @@ import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../../ngtsc/host';
|
||||
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
||||
import {translateStatement, translateType} from '../../../ngtsc/translator';
|
||||
import {translateStatement, translateType, ImportManager} from '../../../ngtsc/translator';
|
||||
import {NgccImportManager} from './ngcc_import_manager';
|
||||
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
|
||||
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
@ -49,6 +50,20 @@ interface DtsClassInfo {
|
||||
compilation: CompileResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A structure that captures information about what needs to be rendered
|
||||
* in a typings file.
|
||||
*
|
||||
* It is created as a result of processing the analysis passed to the renderer.
|
||||
*
|
||||
* The `renderDtsFile()` method consumes it when rendering a typings file.
|
||||
*/
|
||||
class DtsRenderInfo {
|
||||
classInfo: DtsClassInfo[] = [];
|
||||
moduleWithProviders: ModuleWithProvidersInfo[] = [];
|
||||
privateExports: ExportInfo[] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The collected decorators that have become redundant after the compilation
|
||||
* of Ivy static fields. The map is keyed by the container node, such that we
|
||||
@ -71,7 +86,8 @@ export abstract class Renderer {
|
||||
|
||||
renderProgram(
|
||||
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] {
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null): FileInfo[] {
|
||||
const renderedFiles: FileInfo[] = [];
|
||||
|
||||
// Transform the source files.
|
||||
@ -87,16 +103,16 @@ export abstract class Renderer {
|
||||
|
||||
// Transform the .d.ts files
|
||||
if (this.bundle.dts) {
|
||||
const dtsFiles = this.getTypingsFilesToRender(decorationAnalyses);
|
||||
const dtsFiles = this.getTypingsFilesToRender(
|
||||
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
||||
|
||||
// If the dts entry-point is not already there (it did not have compiled classes)
|
||||
// then add it now, to ensure it gets its extra exports rendered.
|
||||
if (!dtsFiles.has(this.bundle.dts.file)) {
|
||||
dtsFiles.set(this.bundle.dts.file, []);
|
||||
dtsFiles.set(this.bundle.dts.file, new DtsRenderInfo());
|
||||
}
|
||||
dtsFiles.forEach(
|
||||
(classes, file) => renderedFiles.push(
|
||||
...this.renderDtsFile(file, classes, privateDeclarationsAnalyses)));
|
||||
(renderInfo, file) => renderedFiles.push(...this.renderDtsFile(file, renderInfo)));
|
||||
}
|
||||
|
||||
return renderedFiles;
|
||||
@ -151,14 +167,12 @@ export abstract class Renderer {
|
||||
return this.renderSourceAndMap(sourceFile, input, outputText);
|
||||
}
|
||||
|
||||
renderDtsFile(
|
||||
dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[],
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] {
|
||||
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
|
||||
const input = this.extractSourceMap(dtsFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX);
|
||||
|
||||
dtsClasses.forEach(dtsClass => {
|
||||
renderInfo.classInfo.forEach(dtsClass => {
|
||||
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
||||
dtsClass.compilation.forEach(declaration => {
|
||||
const type = translateType(declaration.type, importManager);
|
||||
@ -167,26 +181,67 @@ export abstract class Renderer {
|
||||
});
|
||||
});
|
||||
|
||||
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
|
||||
this.addImports(
|
||||
outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile));
|
||||
|
||||
if (dtsFile === this.bundle.dts !.file) {
|
||||
const dtsExports = privateDeclarationsAnalyses.map(e => {
|
||||
if (!e.dtsFrom) {
|
||||
throw new Error(
|
||||
`There is no typings path for ${e.identifier} in ${e.from}.\n` +
|
||||
`We need to add an export for this class to a .d.ts typings file because ` +
|
||||
`Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` +
|
||||
`The simplest fix for this is to ensure that this class is exported from the package's entry-point.`);
|
||||
}
|
||||
return {identifier: e.identifier, from: e.dtsFrom};
|
||||
});
|
||||
this.addExports(outputText, dtsFile.fileName, dtsExports);
|
||||
}
|
||||
this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports);
|
||||
|
||||
|
||||
return this.renderSourceAndMap(dtsFile, input, outputText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the type parameters to the appropriate functions that return `ModuleWithProviders`
|
||||
* structures.
|
||||
*
|
||||
* This function only gets called on typings files, so it doesn't need different implementations
|
||||
* for each bundle format.
|
||||
*/
|
||||
protected addModuleWithProvidersParams(
|
||||
outputText: MagicString, moduleWithProviders: ModuleWithProvidersInfo[],
|
||||
importManager: NgccImportManager): void {
|
||||
moduleWithProviders.forEach(info => {
|
||||
const ngModuleName = (info.ngModule.node as ts.ClassDeclaration).name !.text;
|
||||
const declarationFile = info.declaration.getSourceFile().fileName;
|
||||
const ngModuleFile = info.ngModule.node.getSourceFile().fileName;
|
||||
const importPath = info.ngModule.viaModule ||
|
||||
(declarationFile !== ngModuleFile ?
|
||||
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) :
|
||||
null);
|
||||
const ngModule = getImportString(importManager, importPath, ngModuleName);
|
||||
|
||||
if (info.declaration.type) {
|
||||
const typeName = info.declaration.type && ts.isTypeReferenceNode(info.declaration.type) ?
|
||||
info.declaration.type.typeName :
|
||||
null;
|
||||
if (this.isCoreModuleWithProvidersType(typeName)) {
|
||||
// The declaration already returns `ModuleWithProvider` but it needs the `NgModule` type
|
||||
// parameter adding.
|
||||
outputText.overwrite(
|
||||
info.declaration.type.getStart(), info.declaration.type.getEnd(),
|
||||
`ModuleWithProviders<${ngModule}>`);
|
||||
} else {
|
||||
// The declaration returns an unknown type so we need to convert it to a union that
|
||||
// includes the ngModule property.
|
||||
const originalTypeString = info.declaration.type.getText();
|
||||
outputText.overwrite(
|
||||
info.declaration.type.getStart(), info.declaration.type.getEnd(),
|
||||
`(${originalTypeString})&{ngModule:${ngModule}}`);
|
||||
}
|
||||
} else {
|
||||
// The declaration has no return type so provide one.
|
||||
const lastToken = info.declaration.getLastToken();
|
||||
const insertPoint = lastToken && lastToken.kind === ts.SyntaxKind.SemicolonToken ?
|
||||
lastToken.getStart() :
|
||||
info.declaration.getEnd();
|
||||
outputText.appendLeft(
|
||||
insertPoint,
|
||||
`: ${getImportString(importManager, '@angular/core', 'ModuleWithProviders')}<${ngModule}>`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
||||
void;
|
||||
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
|
||||
@ -302,22 +357,67 @@ export abstract class Renderer {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected getTypingsFilesToRender(analyses: DecorationAnalyses):
|
||||
Map<ts.SourceFile, DtsClassInfo[]> {
|
||||
const dtsMap = new Map<ts.SourceFile, DtsClassInfo[]>();
|
||||
analyses.forEach(compiledFile => {
|
||||
protected getTypingsFilesToRender(
|
||||
decorationAnalyses: DecorationAnalyses,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|
|
||||
null): Map<ts.SourceFile, DtsRenderInfo> {
|
||||
const dtsMap = new Map<ts.SourceFile, DtsRenderInfo>();
|
||||
|
||||
// Capture the rendering info from the decoration analyses
|
||||
decorationAnalyses.forEach(compiledFile => {
|
||||
compiledFile.compiledClasses.forEach(compiledClass => {
|
||||
const dtsDeclaration = this.host.getDtsDeclarationOfClass(compiledClass.declaration);
|
||||
const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
|
||||
if (dtsDeclaration) {
|
||||
const dtsFile = dtsDeclaration.getSourceFile();
|
||||
const classes = dtsMap.get(dtsFile) || [];
|
||||
classes.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||
dtsMap.set(dtsFile, classes);
|
||||
const renderInfo = dtsMap.get(dtsFile) || new DtsRenderInfo();
|
||||
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||
dtsMap.set(dtsFile, renderInfo);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Capture the ModuleWithProviders functions/methods that need updating
|
||||
if (moduleWithProvidersAnalyses !== null) {
|
||||
moduleWithProvidersAnalyses.forEach((moduleWithProvidersToFix, dtsFile) => {
|
||||
const renderInfo = dtsMap.get(dtsFile) || new DtsRenderInfo();
|
||||
renderInfo.moduleWithProviders = moduleWithProvidersToFix;
|
||||
dtsMap.set(dtsFile, renderInfo);
|
||||
});
|
||||
}
|
||||
|
||||
// Capture the private declarations that need to be re-exported
|
||||
if (privateDeclarationsAnalyses.length) {
|
||||
const dtsExports = privateDeclarationsAnalyses.map(e => {
|
||||
if (!e.dtsFrom) {
|
||||
throw new Error(
|
||||
`There is no typings path for ${e.identifier} in ${e.from}.\n` +
|
||||
`We need to add an export for this class to a .d.ts typings file because ` +
|
||||
`Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` +
|
||||
`The simplest fix for this is to ensure that this class is exported from the package's entry-point.`);
|
||||
}
|
||||
return {identifier: e.identifier, from: e.dtsFrom};
|
||||
});
|
||||
const dtsEntryPoint = this.bundle.dts !.file;
|
||||
const renderInfo = dtsMap.get(dtsEntryPoint) || new DtsRenderInfo();
|
||||
renderInfo.privateExports = dtsExports;
|
||||
dtsMap.set(dtsEntryPoint, renderInfo);
|
||||
}
|
||||
|
||||
return dtsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given type is the core Angular `ModuleWithProviders` interface.
|
||||
* @param typeName The type to check.
|
||||
* @returns true if the type is the core Angular `ModuleWithProviders` interface.
|
||||
*/
|
||||
private isCoreModuleWithProvidersType(typeName: ts.EntityName|null) {
|
||||
const id =
|
||||
typeName && ts.isIdentifier(typeName) ? this.host.getImportOfIdentifier(typeName) : null;
|
||||
return (
|
||||
id && id.name === 'ModuleWithProviders' && (this.isCore || id.from === '@angular/core'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -386,7 +486,7 @@ export function renderDefinitions(
|
||||
}
|
||||
|
||||
export function stripExtension(filePath: string): string {
|
||||
return filePath.replace(/\.(js|d\.ts$)/, '');
|
||||
return filePath.replace(/\.(js|d\.ts)$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -399,3 +499,9 @@ function createAssignmentStatement(
|
||||
const receiver = new WrappedNodeExpr(receiverName);
|
||||
return new WritePropExpr(receiver, propName, initializer).toStmt();
|
||||
}
|
||||
|
||||
function getImportString(
|
||||
importManager: ImportManager, importPath: string | null, importName: string) {
|
||||
const importAs = importPath ? importManager.generateNamedImport(importPath, importName) : null;
|
||||
return importAs ? `${importAs.moduleImport}.${importAs.symbol}` : `${importName}`;
|
||||
}
|
||||
|
@ -0,0 +1,401 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {BundleProgram} from '../../src/packages/bundle_program';
|
||||
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
|
||||
|
||||
const TEST_PROGRAM = [
|
||||
{
|
||||
name: '/src/entry-point.js',
|
||||
contents: `
|
||||
export * from './explicit';
|
||||
export * from './any';
|
||||
export * from './implicit';
|
||||
export * from './no-providers';
|
||||
export * from './module';
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/explicit.js',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export class ExplicitInternalModule {}
|
||||
export function explicitInternalFunction() {
|
||||
return {
|
||||
ngModule: ExplicitInternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
export function explicitExternalFunction() {
|
||||
return {
|
||||
ngModule: ExternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
export function explicitLibraryFunction() {
|
||||
return {
|
||||
ngModule: LibraryModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
export class ExplicitClass {
|
||||
static explicitInternalMethod() {
|
||||
return {
|
||||
ngModule: ExplicitInternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
static explicitExternalMethod() {
|
||||
return {
|
||||
ngModule: ExternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
static explicitLibraryMethod() {
|
||||
return {
|
||||
ngModule: LibraryModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/any.js',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export class AnyInternalModule {}
|
||||
export function anyInternalFunction() {
|
||||
return {
|
||||
ngModule: AnyInternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
export function anyExternalFunction() {
|
||||
return {
|
||||
ngModule: ExternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
export function anyLibraryFunction() {
|
||||
return {
|
||||
ngModule: LibraryModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
export class AnyClass {
|
||||
static anyInternalMethod() {
|
||||
return {
|
||||
ngModule: AnyInternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
static anyExternalMethod() {
|
||||
return {
|
||||
ngModule: ExternalModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
static anyLibraryMethod() {
|
||||
return {
|
||||
ngModule: LibraryModule,
|
||||
providers: []
|
||||
};
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/implicit.js',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export class ImplicitInternalModule {}
|
||||
export function implicitInternalFunction() {
|
||||
return {
|
||||
ngModule: ImplicitInternalModule,
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
export function implicitExternalFunction() {
|
||||
return {
|
||||
ngModule: ExternalModule,
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
export function implicitLibraryFunction() {
|
||||
return {
|
||||
ngModule: LibraryModule,
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
export class ImplicitClass {
|
||||
static implicitInternalMethod() {
|
||||
return {
|
||||
ngModule: ImplicitInternalModule,
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
static implicitExternalMethod() {
|
||||
return {
|
||||
ngModule: ExternalModule,
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
static implicitLibraryMethod() {
|
||||
return {
|
||||
ngModule: LibraryModule,
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/no-providers.js',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export class NoProvidersInternalModule {}
|
||||
export function noProvExplicitInternalFunction() {
|
||||
return {ngModule: NoProvidersInternalModule};
|
||||
}
|
||||
export function noProvExplicitExternalFunction() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
export function noProvExplicitLibraryFunction() {
|
||||
return {ngModule: LibraryModule};
|
||||
}
|
||||
export function noProvAnyInternalFunction() {
|
||||
return {ngModule: NoProvidersInternalModule};
|
||||
}
|
||||
export function noProvAnyExternalFunction() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
export function noProvAnyLibraryFunction() {
|
||||
return {ngModule: LibraryModule};
|
||||
}
|
||||
export function noProvImplicitInternalFunction() {
|
||||
return {ngModule: NoProvidersInternalModule};
|
||||
}
|
||||
export function noProvImplicitExternalFunction() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
export function noProvImplicitLibraryFunction() {
|
||||
return {ngModule: LibraryModule};
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/module.js',
|
||||
contents: `
|
||||
export class ExternalModule {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/node_modules/some-library/index.d.ts',
|
||||
contents: 'export declare class LibraryModule {}'
|
||||
},
|
||||
];
|
||||
const TEST_DTS_PROGRAM = [
|
||||
{
|
||||
name: '/typings/entry-point.d.ts',
|
||||
contents: `
|
||||
export * from './explicit';
|
||||
export * from './any';
|
||||
export * from './implicit';
|
||||
export * from './no-providers';
|
||||
export * from './module';
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/explicit.d.ts',
|
||||
contents: `
|
||||
import {ModuleWithProviders} from './core';
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export declare class ExplicitInternalModule {}
|
||||
export declare function explicitInternalFunction(): ModuleWithProviders<ExplicitInternalModule>;
|
||||
export declare function explicitExternalFunction(): ModuleWithProviders<ExternalModule>;
|
||||
export declare function explicitLibraryFunction(): ModuleWithProviders<LibraryModule>;
|
||||
export declare class ExplicitClass {
|
||||
static explicitInternalMethod(): ModuleWithProviders<ExplicitInternalModule>;
|
||||
static explicitExternalMethod(): ModuleWithProviders<ExternalModule>;
|
||||
static explicitLibraryMethod(): ModuleWithProviders<LibraryModule>;
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/any.d.ts',
|
||||
contents: `
|
||||
import {ModuleWithProviders} from './core';
|
||||
export declare class AnyInternalModule {}
|
||||
export declare function anyInternalFunction(): ModuleWithProviders<any>;
|
||||
export declare function anyExternalFunction(): ModuleWithProviders<any>;
|
||||
export declare function anyLibraryFunction(): ModuleWithProviders<any>;
|
||||
export declare class AnyClass {
|
||||
static anyInternalMethod(): ModuleWithProviders<any>;
|
||||
static anyExternalMethod(): ModuleWithProviders<any>;
|
||||
static anyLibraryMethod(): ModuleWithProviders<any>;
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/implicit.d.ts',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export declare class ImplicitInternalModule {}
|
||||
export declare function implicitInternalFunction(): { ngModule: typeof ImplicitInternalModule; providers: never[]; };
|
||||
export declare function implicitExternalFunction(): { ngModule: typeof ExternalModule; providers: never[]; };
|
||||
export declare function implicitLibraryFunction(): { ngModule: typeof LibraryModule; providers: never[]; };
|
||||
export declare class ImplicitClass {
|
||||
static implicitInternalMethod(): { ngModule: typeof ImplicitInternalModule; providers: never[]; };
|
||||
static implicitExternalMethod(): { ngModule: typeof ExternalModule; providers: never[]; };
|
||||
static implicitLibraryMethod(): { ngModule: typeof LibraryModule; providers: never[]; };
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/no-providers.d.ts',
|
||||
contents: `
|
||||
import {ModuleWithProviders} from './core';
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export declare class NoProvidersInternalModule {}
|
||||
export declare function noProvExplicitInternalFunction(): ModuleWithProviders<NoProvidersInternalModule>;
|
||||
export declare function noProvExplicitExternalFunction(): ModuleWithProviders<ExternalModule>;
|
||||
export declare function noProvExplicitLibraryFunction(): ModuleWithProviders<LibraryModule>;
|
||||
export declare function noProvAnyInternalFunction(): ModuleWithProviders<any>;
|
||||
export declare function noProvAnyExternalFunction(): ModuleWithProviders<any>;
|
||||
export declare function noProvAnyLibraryFunction(): ModuleWithProviders<any>;
|
||||
export declare function noProvImplicitInternalFunction(): { ngModule: typeof NoProvidersInternalModule; };
|
||||
export declare function noProvImplicitExternalFunction(): { ngModule: typeof ExternalModule; };
|
||||
export declare function noProvImplicitLibraryFunction(): { ngModule: typeof LibraryModule; };
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/module.d.ts',
|
||||
contents: `
|
||||
export declare class ExternalModule {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/core.d.ts',
|
||||
contents: `
|
||||
|
||||
export declare interface Type<T> {
|
||||
new (...args: any[]): T
|
||||
}
|
||||
export declare type Provider = any;
|
||||
export declare interface ModuleWithProviders<T> {
|
||||
ngModule: Type<T>
|
||||
providers?: Provider[]
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/node_modules/some-library/index.d.ts',
|
||||
contents: 'export declare class LibraryModule {}'
|
||||
},
|
||||
];
|
||||
|
||||
describe('ModuleWithProvidersAnalyzer', () => {
|
||||
describe('analyzeProgram()', () => {
|
||||
let analyses: ModuleWithProvidersAnalyses;
|
||||
let program: ts.Program;
|
||||
let dtsProgram: BundleProgram;
|
||||
let referencesRegistry: NgccReferencesRegistry;
|
||||
|
||||
beforeAll(() => {
|
||||
program = makeTestProgram(...TEST_PROGRAM);
|
||||
dtsProgram = makeTestBundleProgram(TEST_DTS_PROGRAM);
|
||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsProgram);
|
||||
referencesRegistry = new NgccReferencesRegistry(host);
|
||||
|
||||
const analyzer = new ModuleWithProvidersAnalyzer(host, referencesRegistry);
|
||||
analyses = analyzer.analyzeProgram(program);
|
||||
});
|
||||
|
||||
it('should ignore declarations that already have explicit NgModule type params',
|
||||
() => { expect(getAnalysisDescription(analyses, '/typings/explicit.d.ts')).toEqual([]); });
|
||||
|
||||
it('should find declarations that use `any` for the NgModule type param', () => {
|
||||
const anyAnalysis = getAnalysisDescription(analyses, '/typings/any.d.ts');
|
||||
expect(anyAnalysis).toContain(['anyInternalFunction', 'AnyInternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['anyExternalFunction', 'ExternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['anyLibraryFunction', 'LibraryModule', 'some-library']);
|
||||
expect(anyAnalysis).toContain(['anyInternalMethod', 'AnyInternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['anyExternalMethod', 'ExternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['anyLibraryMethod', 'LibraryModule', 'some-library']);
|
||||
});
|
||||
|
||||
it('should track internal module references in the references registry', () => {
|
||||
const declarations = referencesRegistry.getDeclarationMap();
|
||||
const externalModuleDeclaration =
|
||||
getDeclaration(program, '/src/module.js', 'ExternalModule', ts.isClassDeclaration);
|
||||
const libraryModuleDeclaration = getDeclaration(
|
||||
program, '/node_modules/some-library/index.d.ts', 'LibraryModule', ts.isClassDeclaration);
|
||||
expect(declarations.has(externalModuleDeclaration.name !)).toBe(true);
|
||||
expect(declarations.has(libraryModuleDeclaration.name !)).toBe(false);
|
||||
});
|
||||
|
||||
it('should find declarations that have implicit return types', () => {
|
||||
const anyAnalysis = getAnalysisDescription(analyses, '/typings/implicit.d.ts');
|
||||
expect(anyAnalysis).toContain(['implicitInternalFunction', 'ImplicitInternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['implicitExternalFunction', 'ExternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['implicitLibraryFunction', 'LibraryModule', 'some-library']);
|
||||
expect(anyAnalysis).toContain(['implicitInternalMethod', 'ImplicitInternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['implicitExternalMethod', 'ExternalModule', null]);
|
||||
expect(anyAnalysis).toContain(['implicitLibraryMethod', 'LibraryModule', 'some-library']);
|
||||
});
|
||||
|
||||
it('should find declarations that do not specify a `providers` property in the return type',
|
||||
() => {
|
||||
const anyAnalysis = getAnalysisDescription(analyses, '/typings/no-providers.d.ts');
|
||||
expect(anyAnalysis).not.toContain([
|
||||
'noProvExplicitInternalFunction', 'NoProvidersInternalModule'
|
||||
]);
|
||||
expect(anyAnalysis).not.toContain([
|
||||
'noProvExplicitExternalFunction', 'ExternalModule', null
|
||||
]);
|
||||
expect(anyAnalysis).toContain([
|
||||
'noProvAnyInternalFunction', 'NoProvidersInternalModule', null
|
||||
]);
|
||||
expect(anyAnalysis).toContain(['noProvAnyExternalFunction', 'ExternalModule', null]);
|
||||
expect(anyAnalysis).toContain([
|
||||
'noProvAnyLibraryFunction', 'LibraryModule', 'some-library'
|
||||
]);
|
||||
expect(anyAnalysis).toContain([
|
||||
'noProvImplicitInternalFunction', 'NoProvidersInternalModule', null
|
||||
]);
|
||||
expect(anyAnalysis).toContain(['noProvImplicitExternalFunction', 'ExternalModule', null]);
|
||||
expect(anyAnalysis).toContain([
|
||||
'noProvImplicitLibraryFunction', 'LibraryModule', 'some-library'
|
||||
]);
|
||||
});
|
||||
|
||||
function getAnalysisDescription(analyses: ModuleWithProvidersAnalyses, fileName: string) {
|
||||
const file = dtsProgram.program.getSourceFile(fileName) !;
|
||||
const analysis = analyses.get(file);
|
||||
return analysis ?
|
||||
analysis.map(
|
||||
info =>
|
||||
[info.declaration.name !.getText(),
|
||||
(info.ngModule.node as ts.ClassDeclaration).name !.getText(),
|
||||
info.ngModule.viaModule]) :
|
||||
[];
|
||||
}
|
||||
});
|
||||
});
|
@ -84,6 +84,8 @@ export function getFakeCore() {
|
||||
export class InjectionToken {
|
||||
constructor(name: string) {}
|
||||
}
|
||||
|
||||
export interface ModuleWithProviders<T = any> {}
|
||||
`
|
||||
};
|
||||
}
|
||||
|
@ -453,6 +453,7 @@ const TYPINGS_SRC_FILES = [
|
||||
},
|
||||
{name: '/src/class1.js', contents: 'export class Class1 {}\nexport class MissingClass1 {}'},
|
||||
{name: '/src/class2.js', contents: 'export class Class2 {}'},
|
||||
{name: '/src/func1.js', contents: 'export function mooFn() {}'},
|
||||
{name: '/src/internal.js', contents: 'export class InternalClass {}\nexport class Class2 {}'},
|
||||
{name: '/src/missing-class.js', contents: 'export class MissingClass2 {}'}, {
|
||||
name: '/src/flat-file.js',
|
||||
@ -476,6 +477,7 @@ const TYPINGS_DTS_FILES = [
|
||||
contents:
|
||||
`export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';`
|
||||
},
|
||||
{name: '/typings/func1.d.ts', contents: 'export declare function mooFn(): void;'},
|
||||
{
|
||||
name: '/typings/internal.d.ts',
|
||||
contents: `export declare class InternalClass {}\nexport declare class Class2 {}`
|
||||
@ -483,6 +485,54 @@ const TYPINGS_DTS_FILES = [
|
||||
{name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`},
|
||||
];
|
||||
|
||||
const MODULE_WITH_PROVIDERS_PROGRAM = [
|
||||
{
|
||||
name: '/src/functions.js',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
export class SomeService {}
|
||||
export class InternalModule {}
|
||||
export function aNumber() { return 42; }
|
||||
export function aString() { return 'foo'; }
|
||||
export function emptyObject() { return {}; }
|
||||
export function ngModuleIdentifier() { return { ngModule: InternalModule }; }
|
||||
export function ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
|
||||
export function ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
|
||||
export function onlyProviders() { return { providers: [SomeService] }; }
|
||||
export function ngModuleNumber() { return { ngModule: 42 }; }
|
||||
export function ngModuleString() { return { ngModule: 'foo' }; }
|
||||
export function ngModuleObject() { return { ngModule: { foo: 42 } }; }
|
||||
export function externalNgModule() { return { ngModule: ExternalModule }; }
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/methods.js',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
export class SomeService {}
|
||||
export class InternalModule {
|
||||
static aNumber() { return 42; }
|
||||
static aString() { return 'foo'; }
|
||||
static emptyObject() { return {}; }
|
||||
static ngModuleIdentifier() { return { ngModule: InternalModule }; }
|
||||
static ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
|
||||
static ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
|
||||
static onlyProviders() { return { providers: [SomeService] }; }
|
||||
static ngModuleNumber() { return { ngModule: 42 }; }
|
||||
static ngModuleString() { return { ngModule: 'foo' }; }
|
||||
static ngModuleObject() { return { ngModule: { foo: 42 } }; }
|
||||
static externalNgModule() { return { ngModule: ExternalModule }; }
|
||||
|
||||
instanceNgModuleIdentifier() { return { ngModule: InternalModule }; }
|
||||
instanceNgModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
|
||||
instanceNgModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
|
||||
instanceExternalNgModule() { return { ngModule: ExternalModule }; }
|
||||
}
|
||||
`
|
||||
},
|
||||
{name: '/src/module', contents: 'export class ExternalModule {}'},
|
||||
];
|
||||
|
||||
describe('Fesm2015ReflectionHost', () => {
|
||||
|
||||
describe('getDecoratorsOfDeclaration()', () => {
|
||||
@ -1286,10 +1336,20 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
const class1 = getDeclaration(srcProgram, '/src/class1.js', 'Class1', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclarationOfClass(class1);
|
||||
const dtsDeclaration = host.getDtsDeclaration(class1);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
|
||||
});
|
||||
|
||||
it('should find the dts declaration for exported functions', () => {
|
||||
const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES);
|
||||
const dtsProgram = makeTestBundleProgram(TYPINGS_DTS_FILES);
|
||||
const mooFn = getDeclaration(srcProgram, '/src/func1.js', 'mooFn', ts.isFunctionDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dtsProgram);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclaration(mooFn);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/func1.d.ts');
|
||||
});
|
||||
|
||||
it('should return null if there is no matching class in the matching dts file', () => {
|
||||
const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES);
|
||||
const dts = makeTestBundleProgram(TYPINGS_DTS_FILES);
|
||||
@ -1297,7 +1357,7 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
getDeclaration(srcProgram, '/src/class1.js', 'MissingClass1', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
|
||||
|
||||
expect(host.getDtsDeclarationOfClass(missingClass)).toBe(null);
|
||||
expect(host.getDtsDeclaration(missingClass)).toBe(null);
|
||||
});
|
||||
|
||||
it('should return null if there is no matching dts file', () => {
|
||||
@ -1307,7 +1367,7 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
srcProgram, '/src/missing-class.js', 'MissingClass2', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
|
||||
|
||||
expect(host.getDtsDeclarationOfClass(missingClass)).toBe(null);
|
||||
expect(host.getDtsDeclaration(missingClass)).toBe(null);
|
||||
});
|
||||
|
||||
it('should find the dts file that contains a matching class declaration, even if the source files do not match',
|
||||
@ -1318,7 +1378,7 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
getDeclaration(srcProgram, '/src/flat-file.js', 'Class1', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclarationOfClass(class1);
|
||||
const dtsDeclaration = host.getDtsDeclaration(class1);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
|
||||
});
|
||||
|
||||
@ -1329,7 +1389,7 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
getDeclaration(srcProgram, '/src/flat-file.js', 'Class3', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclarationOfClass(class3);
|
||||
const dtsDeclaration = host.getDtsDeclaration(class3);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class3.d.ts');
|
||||
});
|
||||
|
||||
@ -1341,7 +1401,7 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
getDeclaration(srcProgram, '/src/internal.js', 'InternalClass', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclarationOfClass(internalClass);
|
||||
const dtsDeclaration = host.getDtsDeclaration(internalClass);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/internal.d.ts');
|
||||
});
|
||||
|
||||
@ -1355,12 +1415,42 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
getDeclaration(srcProgram, '/src/internal.js', 'Class2', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts);
|
||||
|
||||
const class2DtsDeclaration = host.getDtsDeclarationOfClass(class2);
|
||||
const class2DtsDeclaration = host.getDtsDeclaration(class2);
|
||||
expect(class2DtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class2.d.ts');
|
||||
|
||||
const internalClass2DtsDeclaration = host.getDtsDeclarationOfClass(internalClass2);
|
||||
const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2);
|
||||
expect(internalClass2DtsDeclaration !.getSourceFile().fileName)
|
||||
.toEqual('/typings/class2.d.ts');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getModuleWithProvidersFunctions', () => {
|
||||
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
|
||||
() => {
|
||||
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker());
|
||||
const file = srcProgram.getSourceFile('/src/functions.js') !;
|
||||
const fns = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fns.map(info => [info.declaration.name !.getText(), info.ngModule.text])).toEqual([
|
||||
['ngModuleIdentifier', 'InternalModule'],
|
||||
['ngModuleWithEmptyProviders', 'InternalModule'],
|
||||
['ngModuleWithProviders', 'InternalModule'],
|
||||
['externalNgModule', 'ExternalModule'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should find every static method on exported classes that return an object that looks like a ModuleWithProviders object',
|
||||
() => {
|
||||
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker());
|
||||
const file = srcProgram.getSourceFile('/src/methods.js') !;
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.name !.getText(), fn.ngModule.text])).toEqual([
|
||||
['ngModuleIdentifier', 'InternalModule'],
|
||||
['ngModuleWithEmptyProviders', 'InternalModule'],
|
||||
['ngModuleWithProviders', 'InternalModule'],
|
||||
['externalNgModule', 'ExternalModule'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ import * as ts from 'typescript';
|
||||
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
||||
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
|
||||
import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer';
|
||||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
@ -47,9 +48,9 @@ class TestRenderer extends Renderer {
|
||||
|
||||
function createTestRenderer(
|
||||
packageName: string, files: {name: string, contents: string}[],
|
||||
dtsFile?: {name: string, contents: string}) {
|
||||
dtsFiles?: {name: string, contents: string}[]) {
|
||||
const isCore = packageName === '@angular/core';
|
||||
const bundle = makeTestEntryPointBundle('esm2015', files, dtsFile && [dtsFile]);
|
||||
const bundle = makeTestEntryPointBundle('esm2015', files, dtsFiles);
|
||||
const typeChecker = bundle.src.program.getTypeChecker();
|
||||
const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
@ -57,6 +58,8 @@ function createTestRenderer(
|
||||
new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
|
||||
.analyzeProgram(bundle.src.program);
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
|
||||
const moduleWithProvidersAnalyses =
|
||||
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||
const privateDeclarationsAnalyses =
|
||||
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
|
||||
const renderer = new TestRenderer(host, isCore, bundle);
|
||||
@ -64,7 +67,8 @@ function createTestRenderer(
|
||||
spyOn(renderer, 'addDefinitions').and.callThrough();
|
||||
spyOn(renderer, 'removeDecorators').and.callThrough();
|
||||
|
||||
return {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses};
|
||||
return {renderer, decorationAnalyses, switchMarkerAnalyses, moduleWithProvidersAnalyses,
|
||||
privateDeclarationsAnalyses};
|
||||
}
|
||||
|
||||
|
||||
@ -121,10 +125,11 @@ describe('Renderer', () => {
|
||||
describe('renderProgram()', () => {
|
||||
it('should render the modified contents; and a new map file, if the original provided no map file.',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
expect(result[0].path).toEqual('/dist/file.js');
|
||||
expect(result[0].contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
||||
@ -134,9 +139,11 @@ describe('Renderer', () => {
|
||||
|
||||
|
||||
it('should render as JavaScript', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('test-package', [COMPONENT_PROGRAM]);
|
||||
renderer.renderProgram(decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = createTestRenderer('test-package', [COMPONENT_PROGRAM]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||
@ -154,10 +161,12 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
|
||||
describe('calling abstract methods', () => {
|
||||
it('should call addImports with the source code and info about the core Angular library.',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
||||
@ -167,10 +176,12 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
|
||||
|
||||
it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
|
||||
@ -187,10 +198,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
|
||||
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
|
||||
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||
|
||||
@ -212,14 +225,16 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
describe('source map merging', () => {
|
||||
it('should merge any inline source map from the original file and write the output as an inline source map',
|
||||
() => {
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer(
|
||||
'test-package', [{
|
||||
...INPUT_PROGRAM,
|
||||
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
|
||||
}]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
expect(result[0].path).toEqual('/dist/file.js');
|
||||
expect(result[0].contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
|
||||
@ -230,14 +245,16 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
() => {
|
||||
// Mock out reading the map file from disk
|
||||
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer(
|
||||
'test-package', [{
|
||||
...INPUT_PROGRAM,
|
||||
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
|
||||
}]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
expect(result[0].path).toEqual('/dist/file.js');
|
||||
expect(result[0].contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
||||
@ -259,10 +276,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
contents: `export const NgModule = () => null;`
|
||||
};
|
||||
// The package name of `@angular/core` indicates that we are compiling the core library.
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
|
||||
@ -277,10 +296,11 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
|
||||
};
|
||||
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('@angular/core', [CORE_FILE]);
|
||||
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} = createTestRenderer('@angular/core', [CORE_FILE]);
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toContain(`/*@__PURE__*/ setClassMetadata(`);
|
||||
@ -291,10 +311,12 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
|
||||
describe('rendering typings', () => {
|
||||
it('should render extract types into typings files', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM);
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents)
|
||||
@ -303,30 +325,195 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
});
|
||||
|
||||
it('should render imports into typings files', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM);
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents).toContain(`// ADD IMPORTS\nexport declare class A`);
|
||||
});
|
||||
|
||||
it('should render exports into typings files', () => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM);
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
|
||||
|
||||
// Add a mock export to trigger export rendering
|
||||
privateDeclarationsAnalyses.push(
|
||||
{identifier: 'ComponentB', from: '/src/file.js', dtsFrom: '/typings/b.d.ts'});
|
||||
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !;
|
||||
expect(typingsFile.contents)
|
||||
.toContain(`// ADD EXPORTS\n\n// ADD IMPORTS\nexport declare class A`);
|
||||
});
|
||||
|
||||
it('should fixup functions/methods that return ModuleWithProviders structures', () => {
|
||||
const MODULE_WITH_PROVIDERS_PROGRAM = [
|
||||
{
|
||||
name: '/src/index.js',
|
||||
contents: `
|
||||
import {ExternalModule} from './module';
|
||||
import {LibraryModule} from 'some-library';
|
||||
export class SomeClass {}
|
||||
export class SomeModule {
|
||||
static withProviders1() {
|
||||
return {ngModule: SomeModule};
|
||||
}
|
||||
static withProviders2() {
|
||||
return {ngModule: SomeModule};
|
||||
}
|
||||
static withProviders3() {
|
||||
return {ngModule: SomeClass};
|
||||
}
|
||||
static withProviders4() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
static withProviders5() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
static withProviders6() {
|
||||
return {ngModule: LibraryModule};
|
||||
}
|
||||
static withProviders7() {
|
||||
return {ngModule: SomeModule, providers: []};
|
||||
};
|
||||
static withProviders8() {
|
||||
return {ngModule: SomeModule};
|
||||
}
|
||||
}
|
||||
export function withProviders1() {
|
||||
return {ngModule: SomeModule};
|
||||
}
|
||||
export function withProviders2() {
|
||||
return {ngModule: SomeModule};
|
||||
}
|
||||
export function withProviders3() {
|
||||
return {ngModule: SomeClass};
|
||||
}
|
||||
export function withProviders4() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
export function withProviders5() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
export function withProviders6() {
|
||||
return {ngModule: LibraryModule};
|
||||
}
|
||||
export function withProviders7() {
|
||||
return {ngModule: SomeModule, providers: []};
|
||||
};
|
||||
export function withProviders8() {
|
||||
return {ngModule: SomeModule};
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: '/src/module.js',
|
||||
contents: `
|
||||
export class ExternalModule {
|
||||
static withProviders1() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
static withProviders2() {
|
||||
return {ngModule: ExternalModule};
|
||||
}
|
||||
}`
|
||||
},
|
||||
{
|
||||
name: '/node_modules/some-library/index.d.ts',
|
||||
contents: 'export declare class LibraryModule {}'
|
||||
},
|
||||
];
|
||||
const MODULE_WITH_PROVIDERS_DTS_PROGRAM = [
|
||||
{
|
||||
name: '/typings/index.d.ts',
|
||||
contents: `
|
||||
import {ModuleWithProviders} from '@angular/core';
|
||||
export declare class SomeClass {}
|
||||
export interface MyModuleWithProviders extends ModuleWithProviders {}
|
||||
export declare class SomeModule {
|
||||
static withProviders1(): ModuleWithProviders;
|
||||
static withProviders2(): ModuleWithProviders<any>;
|
||||
static withProviders3(): ModuleWithProviders<SomeClass>;
|
||||
static withProviders4(): ModuleWithProviders;
|
||||
static withProviders5();
|
||||
static withProviders6(): ModuleWithProviders;
|
||||
static withProviders7(): {ngModule: SomeModule, providers: any[]};
|
||||
static withProviders8(): MyModuleWithProviders;
|
||||
}
|
||||
export declare function withProviders1(): ModuleWithProviders;
|
||||
export declare function withProviders2(): ModuleWithProviders<any>;
|
||||
export declare function withProviders3(): ModuleWithProviders<SomeClass>;
|
||||
export declare function withProviders4(): ModuleWithProviders;
|
||||
export declare function withProviders5();
|
||||
export declare function withProviders6(): ModuleWithProviders;
|
||||
export declare function withProviders7(): {ngModule: SomeModule, providers: any[]};
|
||||
export declare function withProviders8(): MyModuleWithProviders;`
|
||||
},
|
||||
{
|
||||
name: '/typings/module.d.ts',
|
||||
contents: `
|
||||
export interface ModuleWithProviders {}
|
||||
export declare class ExternalModule {
|
||||
static withProviders1(): ModuleWithProviders;
|
||||
static withProviders2(): ModuleWithProviders;
|
||||
}`
|
||||
},
|
||||
{
|
||||
name: '/node_modules/some-library/index.d.ts',
|
||||
contents: 'export declare class LibraryModule {}'
|
||||
},
|
||||
];
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses} =
|
||||
createTestRenderer(
|
||||
'test-package', MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM);
|
||||
|
||||
const result = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
moduleWithProvidersAnalyses);
|
||||
|
||||
const typingsFile = result.find(f => f.path === '/typings/index.d.ts') !;
|
||||
|
||||
expect(typingsFile.contents).toContain(`
|
||||
static withProviders1(): ModuleWithProviders<SomeModule>;
|
||||
static withProviders2(): ModuleWithProviders<SomeModule>;
|
||||
static withProviders3(): ModuleWithProviders<SomeClass>;
|
||||
static withProviders4(): ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||
static withProviders5(): ɵngcc1.ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||
static withProviders6(): ModuleWithProviders<ɵngcc2.LibraryModule>;
|
||||
static withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
|
||||
static withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
|
||||
expect(typingsFile.contents).toContain(`
|
||||
export declare function withProviders1(): ModuleWithProviders<SomeModule>;
|
||||
export declare function withProviders2(): ModuleWithProviders<SomeModule>;
|
||||
export declare function withProviders3(): ModuleWithProviders<SomeClass>;
|
||||
export declare function withProviders4(): ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||
export declare function withProviders5(): ɵngcc1.ModuleWithProviders<ɵngcc0.ExternalModule>;
|
||||
export declare function withProviders6(): ModuleWithProviders<ɵngcc2.LibraryModule>;
|
||||
export declare function withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule};
|
||||
export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
|
||||
|
||||
expect(renderer.addImports).toHaveBeenCalledWith(jasmine.any(MagicString), [
|
||||
{name: './module', as: 'ɵngcc0'},
|
||||
{name: '@angular/core', as: 'ɵngcc1'},
|
||||
{name: 'some-library', as: 'ɵngcc2'},
|
||||
]);
|
||||
|
||||
|
||||
// The following expectation checks that we do not mistake `ModuleWithProviders` types
|
||||
// that are not imported from `@angular/core`.
|
||||
const typingsFile2 = result.find(f => f.path === '/typings/module.d.ts') !;
|
||||
expect(typingsFile2.contents).toContain(`
|
||||
static withProviders1(): (ModuleWithProviders)&{ngModule:ExternalModule};
|
||||
static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -109,7 +109,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
const valueContext = node.getSourceFile();
|
||||
|
||||
let typeContext = valueContext;
|
||||
const typeNode = this.reflector.getDtsDeclarationOfClass(node);
|
||||
const typeNode = this.reflector.getDtsDeclaration(node);
|
||||
if (typeNode !== null) {
|
||||
typeContext = typeNode.getSourceFile();
|
||||
}
|
||||
@ -183,8 +183,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
return toR3Reference(valueRef, valueRef, valueContext, valueContext);
|
||||
} else {
|
||||
let typeRef = valueRef;
|
||||
let typeNode = this.reflector.getDtsDeclarationOfClass(typeRef.node);
|
||||
if (typeNode !== null) {
|
||||
let typeNode = this.reflector.getDtsDeclaration(typeRef.node);
|
||||
if (typeNode !== null && ts.isClassDeclaration(typeNode)) {
|
||||
typeRef = new ResolvedReference(typeNode, typeNode.name !);
|
||||
}
|
||||
return toR3Reference(valueRef, typeRef, valueContext, typeContext);
|
||||
@ -197,9 +197,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
*/
|
||||
private _extractModuleFromModuleWithProvidersFn(node: ts.FunctionDeclaration|
|
||||
ts.MethodDeclaration): ts.Expression|null {
|
||||
const type = node.type;
|
||||
const type = node.type || null;
|
||||
return type &&
|
||||
(this._reflectModuleFromTypeParam(type) || this._reflectModuleFromLiteralType(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
||||
* `ModuleWithProviders<T>`
|
||||
* @param type The type to reflect on.
|
||||
* @returns the identifier of the NgModule type if found, or null otherwise.
|
||||
*/
|
||||
private _reflectModuleFromTypeParam(type: ts.TypeNode): ts.Expression|null {
|
||||
// Examine the type of the function to see if it's a ModuleWithProviders reference.
|
||||
if (type === undefined || !ts.isTypeReferenceNode(type) || !ts.isIdentifier(type.typeName)) {
|
||||
if (!ts.isTypeReferenceNode(type) || !ts.isIdentifier(type.typeName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -226,6 +237,32 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
return typeNodeToValueExpr(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
||||
* `A|B|{ngModule: T}|C`.
|
||||
* @param type The type to reflect on.
|
||||
* @returns the identifier of the NgModule type if found, or null otherwise.
|
||||
*/
|
||||
private _reflectModuleFromLiteralType(type: ts.TypeNode): ts.Expression|null {
|
||||
if (!ts.isIntersectionTypeNode(type)) {
|
||||
return null;
|
||||
}
|
||||
for (const t of type.types) {
|
||||
if (ts.isTypeLiteralNode(t)) {
|
||||
for (const m of t.members) {
|
||||
const ngModuleType = ts.isPropertySignature(m) && ts.isIdentifier(m.name) &&
|
||||
m.name.text === 'ngModule' && m.type ||
|
||||
null;
|
||||
const ngModuleExpression = ngModuleType && typeNodeToValueExpr(ngModuleType);
|
||||
if (ngModuleExpression) {
|
||||
return ngModuleExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a list of `Reference`s from a resolved metadata value.
|
||||
*/
|
||||
|
@ -448,7 +448,7 @@ export interface ReflectionHost {
|
||||
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null;
|
||||
|
||||
/**
|
||||
* Take an exported declaration of a class (maybe downleveled to a variable) and look up the
|
||||
* Take an exported declaration (maybe a class down-leveled to a variable) and look up the
|
||||
* declaration of its type in a separate .d.ts tree.
|
||||
*
|
||||
* This function is allowed to return `null` if the current compilation unit does not have a
|
||||
@ -456,8 +456,8 @@ export interface ReflectionHost {
|
||||
* are produced only during the emit of such a compilation. When compiling .js code, however,
|
||||
* there is frequently a parallel .d.ts tree which this method exposes.
|
||||
*
|
||||
* Note that the `ts.ClassDeclaration` returned from this function may not be from the same
|
||||
* Note that the `ts.Declaration` returned from this function may not be from the same
|
||||
* `ts.Program` as the input declaration.
|
||||
*/
|
||||
getDtsDeclarationOfClass(declaration: ts.Declaration): ts.ClassDeclaration|null;
|
||||
getDtsDeclaration(declaration: ts.Declaration): ts.Declaration|null;
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
|
||||
return declaration.initializer || null;
|
||||
}
|
||||
|
||||
getDtsDeclarationOfClass(_: ts.Declaration): ts.ClassDeclaration|null { return null; }
|
||||
getDtsDeclaration(_: ts.Declaration): ts.Declaration|null { return null; }
|
||||
|
||||
/**
|
||||
* Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AttributeMarker, InitialStylingFlags} from '@angular/compiler/src/core';
|
||||
import {AttributeMarker} from '@angular/compiler/src/core';
|
||||
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
@ -48,17 +48,15 @@ describe('compiler compliance', () => {
|
||||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ["title", "Hello"];
|
||||
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
|
||||
const $c3$ = ["cx", "20", "cy", "30", "r", "50"];
|
||||
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
|
||||
const $c2$ = ["cx", "20", "cy", "30", "r", "50"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $c1$);
|
||||
$r3$.ɵelementStyling($c2$);
|
||||
$r3$.ɵnamespaceSVG();
|
||||
$r3$.ɵelementStart(1, "svg");
|
||||
$r3$.ɵelement(2, "circle", $c3$);
|
||||
$r3$.ɵelement(2, "circle", $c2$);
|
||||
$r3$.ɵelementEnd();
|
||||
$r3$.ɵnamespaceHTML();
|
||||
$r3$.ɵelementStart(3, "p");
|
||||
@ -100,13 +98,11 @@ describe('compiler compliance', () => {
|
||||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ["title", "Hello"];
|
||||
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
|
||||
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $c1$);
|
||||
$r3$.ɵelementStyling($c2$);
|
||||
$r3$.ɵnamespaceMathML();
|
||||
$r3$.ɵelementStart(1, "math");
|
||||
$r3$.ɵelement(2, "infinity");
|
||||
@ -150,13 +146,11 @@ describe('compiler compliance', () => {
|
||||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ["title", "Hello"];
|
||||
const $c2$ = ["my-app", ${InitialStylingFlags.VALUES_MODE}, "my-app", true];
|
||||
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $c1$);
|
||||
$r3$.ɵelementStyling($c2$);
|
||||
$r3$.ɵtext(1, "Hello ");
|
||||
$r3$.ɵelementStart(2, "b");
|
||||
$r3$.ɵtext(3, "World");
|
||||
@ -486,8 +480,8 @@ describe('compiler compliance', () => {
|
||||
const factory =
|
||||
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
|
||||
const template = `
|
||||
const _c0 = ["error"];
|
||||
const _c1 = ["background-color"];
|
||||
const $e0_classBindings$ = ["error"];
|
||||
const $e0_styleBindings$ = ["background-color"];
|
||||
…
|
||||
MyComponent.ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[["my-component"]],
|
||||
factory: function MyComponent_Factory(t){
|
||||
@ -498,7 +492,7 @@ describe('compiler compliance', () => {
|
||||
template: function MyComponent_Template(rf,ctx){
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div");
|
||||
$r3$.ɵelementStyling(_c0, _c1);
|
||||
$r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
@ -1092,156 +1086,224 @@ describe('compiler compliance', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should support content projection in root template', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||
describe('content projection', () => {
|
||||
|
||||
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
|
||||
export class SimpleComponent {}
|
||||
|
||||
@Component({
|
||||
selector: 'complex',
|
||||
template: \`
|
||||
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
|
||||
<div id="second"><ng-content SELECT="span[title=toSecond]"></ng-content></div>\`
|
||||
it('should support content projection in root template', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||
|
||||
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
|
||||
export class SimpleComponent {}
|
||||
|
||||
@Component({
|
||||
selector: 'complex',
|
||||
template: \`
|
||||
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
|
||||
<div id="second"><ng-content SELECT="span[title=toSecond]"></ng-content></div>\`
|
||||
})
|
||||
export class ComplexComponent { }
|
||||
|
||||
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
|
||||
export class MyModule {}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<simple>content</simple> <complex></complex>'
|
||||
})
|
||||
export class ComplexComponent { }
|
||||
export class MyApp {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
|
||||
export class MyModule {}
|
||||
const SimpleComponentDefinition = `
|
||||
SimpleComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
selectors: [["simple"]],
|
||||
factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); },
|
||||
consts: 2,
|
||||
vars: 0,
|
||||
template: function SimpleComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojectionDef();
|
||||
$r3$.ɵelementStart(0, "div");
|
||||
$r3$.ɵprojection(1);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});`;
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<simple>content</simple> <complex></complex>'
|
||||
})
|
||||
export class MyApp {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const ComplexComponentDefinition = `
|
||||
const $c3$ = ["id","first"];
|
||||
const $c4$ = ["id","second"];
|
||||
const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]];
|
||||
const $c2$ = ["span[title=toFirst]", "span[title=toSecond]"];
|
||||
…
|
||||
ComplexComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ComplexComponent,
|
||||
selectors: [["complex"]],
|
||||
factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); },
|
||||
consts: 4,
|
||||
vars: 0,
|
||||
template: function ComplexComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojectionDef($c1$, $c2$);
|
||||
$r3$.ɵelementStart(0, "div", $c3$);
|
||||
$r3$.ɵprojection(1, 1);
|
||||
$r3$.ɵelementEnd();
|
||||
$r3$.ɵelementStart(2, "div", $c4$);
|
||||
$r3$.ɵprojection(3, 2);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const SimpleComponentDefinition = `
|
||||
SimpleComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
selectors: [["simple"]],
|
||||
factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); },
|
||||
consts: 2,
|
||||
vars: 0,
|
||||
template: function SimpleComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojectionDef();
|
||||
$r3$.ɵelementStart(0, "div");
|
||||
$r3$.ɵprojection(1);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});`;
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
const ComplexComponentDefinition = `
|
||||
const $c3$ = ["id","first"];
|
||||
const $c4$ = ["id","second"];
|
||||
const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]];
|
||||
const $c2$ = ["span[title=toFirst]", "span[title=toSecond]"];
|
||||
…
|
||||
ComplexComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ComplexComponent,
|
||||
selectors: [["complex"]],
|
||||
factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); },
|
||||
consts: 4,
|
||||
vars: 0,
|
||||
template: function ComplexComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojectionDef($c1$, $c2$);
|
||||
$r3$.ɵelementStart(0, "div", $c3$);
|
||||
expectEmit(
|
||||
result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
|
||||
expectEmit(
|
||||
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
|
||||
});
|
||||
|
||||
it('should support content projection in nested templates', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`
|
||||
<div id="second" *ngIf="visible">
|
||||
<ng-content SELECT="span[title=toFirst]"></ng-content>
|
||||
</div>
|
||||
<div id="third" *ngIf="visible">
|
||||
No ng-content, no instructions generated.
|
||||
</div>
|
||||
<ng-template>
|
||||
'*' selector: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
\`,
|
||||
})
|
||||
class Cmp {}
|
||||
|
||||
@NgModule({ declarations: [Cmp] })
|
||||
class Module {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const output = `
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $_c1$ = ["id", "second"];
|
||||
function Cmp_div_Template_0(rf, ctx) { if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $_c1$);
|
||||
$r3$.ɵprojection(1, 1);
|
||||
$r3$.ɵelementEnd();
|
||||
$r3$.ɵelementStart(2, "div", $c4$);
|
||||
$r3$.ɵprojection(3, 2);
|
||||
} }
|
||||
const $_c4$ = ["id", "third"];
|
||||
function Cmp_div_Template_1(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $_c4$);
|
||||
$r3$.ɵtext(1, " No ng-content, no instructions generated. ");
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
|
||||
expectEmit(
|
||||
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
|
||||
});
|
||||
|
||||
it('should support content projection in nested templates', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`
|
||||
<div id="second" *ngIf="visible">
|
||||
<ng-content SELECT="span[title=toFirst]"></ng-content>
|
||||
</div>
|
||||
<div id="third" *ngIf="visible">
|
||||
No ng-content, no instructions generated.
|
||||
</div>
|
||||
<ng-template>
|
||||
'*' selector: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
\`,
|
||||
})
|
||||
class Cmp {}
|
||||
|
||||
@NgModule({ declarations: [Cmp] })
|
||||
class Module {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const output = `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c1$ = ["id", "second"];
|
||||
const $_c2$ = [[["span", "title", "tofirst"]]];
|
||||
const $_c3$ = ["span[title=toFirst]"];
|
||||
function Cmp_div_Template_0(rf, ctx) { if (rf & 1) {
|
||||
$r3$.ɵprojectionDef($_c2$, $_c3$);
|
||||
$r3$.ɵelementStart(0, "div", $_c1$);
|
||||
$r3$.ɵprojection(1, 1);
|
||||
$r3$.ɵelementEnd();
|
||||
} }
|
||||
const $_c4$ = ["id", "third"];
|
||||
function Cmp_div_Template_1(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $_c4$);
|
||||
$r3$.ɵtext(1, " No ng-content, no instructions generated. ");
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
}
|
||||
function Cmp_ng_template_Template_2(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojectionDef();
|
||||
$r3$.ɵtext(0, " '*' selector: ");
|
||||
$r3$.ɵprojection(1);
|
||||
function Cmp_ng_template_Template_2(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵtext(0, " '*' selector: ");
|
||||
$r3$.ɵprojection(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
…
|
||||
template: function Cmp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵtemplate(0, Cmp_div_Template_0, 2, 0, "div", $_c0$);
|
||||
$r3$.ɵtemplate(1, Cmp_div_Template_1, 2, 0, "div", $_c0$);
|
||||
$r3$.ɵtemplate(2, Cmp_ng_template_Template_2, 2, 0, "ng-template");
|
||||
const $_c2$ = [[["span", "title", "tofirst"]]];
|
||||
const $_c3$ = ["span[title=toFirst]"];
|
||||
…
|
||||
template: function Cmp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojectionDef($_c2$, $_c3$);
|
||||
$r3$.ɵtemplate(0, Cmp_div_Template_0, 2, 0, "div", $_c0$);
|
||||
$r3$.ɵtemplate(1, Cmp_div_Template_1, 2, 0, "div", $_c0$);
|
||||
$r3$.ɵtemplate(2, Cmp_ng_template_Template_2, 2, 0, "ng-template");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementProperty(0, "ngIf", $r3$.ɵbind(ctx.visible));
|
||||
$r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible));
|
||||
}
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementProperty(0, "ngIf", $r3$.ɵbind(ctx.visible));
|
||||
$r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible));
|
||||
}
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
const {source} = compile(files, angularFiles);
|
||||
expectEmit(source, output, 'Invalid content projection instructions generated');
|
||||
const {source} = compile(files, angularFiles);
|
||||
expectEmit(source, output, 'Invalid content projection instructions generated');
|
||||
});
|
||||
|
||||
it('should support content projection in both the root and nested templates', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`
|
||||
<ng-content select="[id=toMainBefore]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toTemplate]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toNestedTemplate]"></ng-content>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template>
|
||||
'*' selector in a template: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
<ng-content select="[id=toMainAfter]"></ng-content>
|
||||
\`,
|
||||
})
|
||||
class Cmp {}
|
||||
|
||||
@NgModule({ declarations: [Cmp] })
|
||||
class Module {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const output = `
|
||||
function Cmp_ng_template_ng_template_Template_1(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojection(0, 4);
|
||||
}
|
||||
}
|
||||
function Cmp_ng_template_Template_1(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojection(0, 3);
|
||||
$r3$.ɵtemplate(1, Cmp_ng_template_ng_template_Template_1, 1, 0, "ng-template");
|
||||
}
|
||||
}
|
||||
function Cmp_ng_template_Template_2(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵtext(0, " '*' selector in a template: ");
|
||||
$r3$.ɵprojection(1);
|
||||
}
|
||||
}
|
||||
const $_c0$ = [[["", "id", "tomainbefore"]], [["", "id", "tomainafter"]], [["", "id", "totemplate"]], [["", "id", "tonestedtemplate"]]];
|
||||
const $_c1$ = ["[id=toMainBefore]", "[id=toMainAfter]", "[id=toTemplate]", "[id=toNestedTemplate]"];
|
||||
…
|
||||
template: function Cmp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵprojectionDef($_c2$, $_c3$);
|
||||
$r3$.ɵprojection(0, 1);
|
||||
$r3$.ɵtemplate(1, Cmp_ng_template_Template_1, 2, 0, "ng-template");
|
||||
$r3$.ɵtemplate(2, Cmp_ng_template_Template_2, 2, 0, "ng-template");
|
||||
$r3$.ɵprojection(3, 2);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const {source} = compile(files, angularFiles);
|
||||
expectEmit(source, output, 'Invalid content projection instructions generated');
|
||||
});
|
||||
});
|
||||
|
||||
describe('queries', () => {
|
||||
|
@ -236,7 +236,7 @@ describe('compiler compliance: directives', () => {
|
||||
|
||||
const MyComponentDefinition = `
|
||||
…
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $_c1$ = ["directiveA", ""];
|
||||
function MyComponent_ng_container_Template_0(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AttributeMarker} from '@angular/compiler/src/core';
|
||||
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler';
|
||||
@ -393,7 +394,7 @@ describe('i18n support in the view compiler', () => {
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["ngFor", "", 1, "ngForOf"];
|
||||
const $_c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"];
|
||||
/**
|
||||
* @desc d
|
||||
* @meaning m
|
||||
@ -522,7 +523,7 @@ describe('i18n support in the view compiler', () => {
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["ngFor", "", 1, "ngForOf"];
|
||||
const $_c0$ = ["ngFor", "", ${AttributeMarker.SelectOnly}, "ngForOf"];
|
||||
/**
|
||||
* @desc d
|
||||
* @meaning m
|
||||
@ -922,7 +923,7 @@ describe('i18n support in the view compiler', () => {
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_7679414751795588050$$APP_SPEC_TS__1$ = goog.getMsg(" Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}", {
|
||||
"interpolation": "\uFFFD0\uFFFD",
|
||||
"startTagDiv": "\uFFFD#3\uFFFD",
|
||||
@ -976,7 +977,7 @@ describe('i18n support in the view compiler', () => {
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["src", "logo.png"];
|
||||
const $_c1$ = [1, "ngIf"];
|
||||
const $_c1$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
function MyComponent_img_Template_1(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelement(0, "img", $_c0$);
|
||||
@ -1043,7 +1044,7 @@ describe('i18n support in the view compiler', () => {
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
function MyComponent_div_div_Template_4(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵi18nStart(0, $I18N_EXTERNAL_1221890473527419724$$APP_SPEC_TS_0$, 2);
|
||||
@ -1136,7 +1137,7 @@ describe('i18n support in the view compiler', () => {
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_119975189388320493$$APP_SPEC_TS__1$ = goog.getMsg("Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}", {
|
||||
"startTagSpan": "\uFFFD#2\uFFFD",
|
||||
"interpolation": "\uFFFD0\uFFFD",
|
||||
@ -1259,23 +1260,21 @@ describe('i18n support in the view compiler', () => {
|
||||
`;
|
||||
|
||||
const output = String.raw `
|
||||
const $_c0$ = ["myClass", 1, "myClass", true];
|
||||
const $_c0$ = [${AttributeMarker.Classes}, "myClass"];
|
||||
const $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$ = goog.getMsg("Text #1");
|
||||
const $_c1$ = ["padding", 1, "padding", "10px"];
|
||||
const $_c1$ = [${AttributeMarker.Styles}, "padding", "10px"];
|
||||
const $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$ = goog.getMsg("Text #2");
|
||||
…
|
||||
consts: 4,
|
||||
vars: 0,
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "span");
|
||||
$r3$.ɵelementStart(0, "span", $_c0$);
|
||||
$r3$.ɵi18nStart(1, $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$);
|
||||
$r3$.ɵelementStyling($_c0$);
|
||||
$r3$.ɵi18nEnd();
|
||||
$r3$.ɵelementEnd();
|
||||
$r3$.ɵelementStart(2, "span");
|
||||
$r3$.ɵelementStart(2, "span", $_c1$);
|
||||
$r3$.ɵi18nStart(3, $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$);
|
||||
$r3$.ɵelementStyling(null, $_c1$);
|
||||
$r3$.ɵi18nEnd();
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
@ -1701,7 +1700,7 @@ describe('i18n support in the view compiler', () => {
|
||||
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_0$, {
|
||||
"VAR_SELECT": "\uFFFD0\uFFFD"
|
||||
});
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $_c1$ = ["title", "icu only"];
|
||||
const $MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}");
|
||||
const $I18N_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_8806993169187953163$$APP_SPEC_TS__3$, {
|
||||
@ -1942,7 +1941,7 @@ describe('i18n support in the view compiler', () => {
|
||||
const $I18N_APP_SPEC_TS_2$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS_2$, {
|
||||
"VAR_SELECT": "\uFFFD1\uFFFD"
|
||||
});
|
||||
const $_c3$ = [1, "ngIf"];
|
||||
const $_c3$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}");
|
||||
const $I18N_APP_SPEC_TS__4$ = $r3$.ɵi18nPostprocess($MSG_APP_SPEC_TS__4$, {
|
||||
"VAR_SELECT": "\uFFFD0:1\uFFFD"
|
||||
@ -2050,7 +2049,7 @@ describe('i18n support in the view compiler', () => {
|
||||
const $I18N_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7842238767399919809$$APP_SPEC_TS_1$, {
|
||||
"VAR_SELECT": "\uFFFD0\uFFFD"
|
||||
});
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}");
|
||||
const $I18N_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7068143081688428291$$APP_SPEC_TS__3$, {
|
||||
"VAR_SELECT": "\uFFFD0:1\uFFFD"
|
||||
@ -2113,7 +2112,7 @@ describe('i18n support in the view compiler', () => {
|
||||
const $I18N_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$ = $r3$.ɵi18nPostprocess($MSG_EXTERNAL_7825031864601787094$$APP_SPEC_TS_1$, {
|
||||
"VAR_SELECT": "\uFFFD0\uFFFD"
|
||||
});
|
||||
const $_c0$ = [1, "ngIf"];
|
||||
const $_c0$ = [${AttributeMarker.SelectOnly}, "ngIf"];
|
||||
const $MSG_EXTERNAL_2310343208266678305$$APP_SPEC_TS__3$ = goog.getMsg("{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {$interpolation}}}", {
|
||||
"interpolation": "\uFFFD1:1\uFFFD"
|
||||
});
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AttributeMarker, InitialStylingFlags, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||
import {AttributeMarker, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
@ -366,8 +366,8 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "style"];
|
||||
const $e0_styling$ = ["opacity","width","height",${InitialStylingFlags.VALUES_MODE},"opacity","1"];
|
||||
const $_c0$ = [${AttributeMarker.Styles}, "opacity", "1", ${AttributeMarker.SelectOnly}, "style"];
|
||||
const $_c1$ = ["width", "height"];
|
||||
…
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
@ -379,14 +379,14 @@ describe('compiler compliance: styling', () => {
|
||||
vars: 1,
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $e0_attrs$);
|
||||
$r3$.ɵelementStyling(null, $e0_styling$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵelementStart(0, "div", $_c0$);
|
||||
$r3$.ɵelementStyling(null, $_c1$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStylingMap(0, null, $ctx$.myStyleExp);
|
||||
$r3$.ɵelementStyleProp(0, 1, $ctx$.myWidth);
|
||||
$r3$.ɵelementStyleProp(0, 2, $ctx$.myHeight);
|
||||
$r3$.ɵelementStyleProp(0, 0, $ctx$.myWidth);
|
||||
$r3$.ɵelementStyleProp(0, 1, $ctx$.myHeight);
|
||||
$r3$.ɵelementStylingApply(0);
|
||||
$r3$.ɵelementAttribute(0, "style", $r3$.ɵbind("border-width: 10px"), $r3$.ɵsanitizeStyle);
|
||||
}
|
||||
@ -421,7 +421,7 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ["background-image"];
|
||||
const $_c0$ = ["background-image"];
|
||||
export class MyComponent {
|
||||
constructor() {
|
||||
this.myImage = 'url(foo.jpg)';
|
||||
@ -456,7 +456,6 @@ describe('compiler compliance: styling', () => {
|
||||
});
|
||||
|
||||
it('should support [style.foo.suffix] style bindings with a suffix', () => {
|
||||
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
@ -476,7 +475,7 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $e0_styles$= ["font-size"];
|
||||
const $e0_styles$ = ["font-size"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
@ -564,8 +563,8 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class"];
|
||||
const $e0_cd$ = ["grape","apple","orange",${InitialStylingFlags.VALUES_MODE},"grape",true];
|
||||
const $e0_attrs$ = [${AttributeMarker.Classes}, "grape", ${AttributeMarker.SelectOnly}, "class"];
|
||||
const $e0_bindings$ = ["apple", "orange"];
|
||||
…
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
@ -578,13 +577,13 @@ describe('compiler compliance: styling', () => {
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $e0_attrs$);
|
||||
$r3$.ɵelementStyling($e0_cd$);
|
||||
$r3$.ɵelementStyling($e0_bindings$);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStylingMap(0, $ctx$.myClassExp);
|
||||
$r3$.ɵelementClassProp(0, 1, $ctx$.yesToApple);
|
||||
$r3$.ɵelementClassProp(0, 2, $ctx$.yesToOrange);
|
||||
$r3$.ɵelementClassProp(0, 0, $ctx$.yesToApple);
|
||||
$r3$.ɵelementClassProp(0, 1, $ctx$.yesToOrange);
|
||||
$r3$.ɵelementStylingApply(0);
|
||||
$r3$.ɵelementAttribute(0, "class", $r3$.ɵbind("banana"));
|
||||
}
|
||||
@ -606,7 +605,7 @@ describe('compiler compliance: styling', () => {
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="foo"
|
||||
template: \`<div class=" foo "
|
||||
style="width:100px"
|
||||
[attr.class]="'round'"
|
||||
[attr.style]="'height:100px'"></div>\`
|
||||
@ -620,9 +619,7 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $e0_attrs$ = [${AttributeMarker.SelectOnly}, "class", "style"];
|
||||
const $e0_cd$ = ["foo",${InitialStylingFlags.VALUES_MODE},"foo",true];
|
||||
const $e0_sd$ = ["width",${InitialStylingFlags.VALUES_MODE},"width","100px"];
|
||||
const $e0_attrs$ = [${AttributeMarker.Classes}, "foo", ${AttributeMarker.Styles}, "width", "100px", ${AttributeMarker.SelectOnly}, "class", "style"];
|
||||
…
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
@ -635,7 +632,6 @@ describe('compiler compliance: styling', () => {
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div", $e0_attrs$);
|
||||
$r3$.ɵelementStyling($e0_cd$, $e0_sd$);
|
||||
$r3$.ɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
@ -765,10 +761,13 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $e0_classBindings$ = ["foo"];
|
||||
const $e0_styleBindings$ = ["bar", "baz"];
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStart(0, "div");
|
||||
$r3$.ɵelementStyling($e0_styling$, $e1_styling$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$, $r3$.ɵdefaultStyleSanitizer);
|
||||
$r3$.ɵpipe(1, "pipe");
|
||||
$r3$.ɵpipe(2, "pipe");
|
||||
$r3$.ɵpipe(3, "pipe");
|
||||
@ -828,16 +827,18 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ["foo", "baz", ${InitialStylingFlags.VALUES_MODE}, "foo", true, "baz", true];
|
||||
const _c1 = ["width", "height", "color", ${InitialStylingFlags.VALUES_MODE}, "width", "200px", "height", "500px"];
|
||||
const $e0_attrs$ = [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"];
|
||||
const $e0_classBindings$ = ["foo"];
|
||||
const $e0_styleBindings$ = ["color"];
|
||||
…
|
||||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
$r3$.ɵelementHostAttrs(ctx, $e0_attrs$);
|
||||
$r3$.ɵelementStyling($e0_classBindings$, $e0_styleBindings$, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStylingMap(elIndex, ctx.myClass, ctx.myStyle, ctx);
|
||||
$r3$.ɵelementStyleProp(elIndex, 2, ctx.myColorProp, null, ctx);
|
||||
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myColorProp, null, ctx);
|
||||
$r3$.ɵelementClassProp(elIndex, 0, ctx.myFooClass, ctx);
|
||||
$r3$.ɵelementStylingApply(elIndex, ctx);
|
||||
}
|
||||
@ -959,10 +960,10 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ["foo"];
|
||||
const _c1 = ["width"];
|
||||
const _c2 = ["bar"];
|
||||
const _c3 = ["height"];
|
||||
const $widthDir_classes$ = ["foo"];
|
||||
const $widthDir_styles$ = ["width"];
|
||||
const $heightDir_classes$ = ["bar"];
|
||||
const $heightDir_styles$ = ["height"];
|
||||
…
|
||||
function ClassDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
@ -976,7 +977,7 @@ describe('compiler compliance: styling', () => {
|
||||
…
|
||||
function WidthDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStyling(_c0, _c1, null, ctx);
|
||||
$r3$.ɵelementStyling($widthDir_classes$, $widthDir_styles$, null, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myWidth, null, ctx);
|
||||
@ -987,7 +988,7 @@ describe('compiler compliance: styling', () => {
|
||||
…
|
||||
function HeightDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵelementStyling(_c2, _c3, null, ctx);
|
||||
$r3$.ɵelementStyling($heightDir_classes$, $heightDir_styles$, null, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementStyleProp(elIndex, 0, ctx.myHeight, null, ctx);
|
||||
@ -1014,7 +1015,8 @@ describe('compiler compliance: styling', () => {
|
||||
template: '',
|
||||
host: {
|
||||
'style': 'width:200px; height:500px',
|
||||
'class': 'foo baz'
|
||||
'class': 'foo baz',
|
||||
'title': 'foo title'
|
||||
}
|
||||
})
|
||||
export class MyComponent {
|
||||
@ -1029,6 +1031,9 @@ describe('compiler compliance: styling', () => {
|
||||
|
||||
@HostBinding('title')
|
||||
title = 'some title';
|
||||
|
||||
@Input('name')
|
||||
name = '';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
@ -1038,13 +1043,13 @@ describe('compiler compliance: styling', () => {
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $_c0$ = ["foo", "baz", ${InitialStylingFlags.VALUES_MODE}, "foo", true, "baz", true];
|
||||
const $_c1$ = ["width", "height", ${InitialStylingFlags.VALUES_MODE}, "width", "200px", "height", "500px"];
|
||||
const $_c0$ = [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"];
|
||||
…
|
||||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵallocHostVars(2);
|
||||
$r3$.ɵelementStyling($_c0$, $_c1$, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
$r3$.ɵelementHostAttrs(ctx, $_c0$);
|
||||
$r3$.ɵelementStyling(null, null, $r3$.ɵdefaultStyleSanitizer, ctx);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.id), null, true);
|
||||
|
@ -441,6 +441,39 @@ describe('ngtsc behavioral tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should unwrap a ModuleWithProviders-like function if a matching literal type is provided for it',
|
||||
() => {
|
||||
env.tsconfig();
|
||||
env.write(`test.ts`, `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule} from 'router';
|
||||
|
||||
@NgModule({imports: [RouterModule.forRoot()]})
|
||||
export class TestModule {}
|
||||
`);
|
||||
|
||||
env.write('node_modules/router/index.d.ts', `
|
||||
import {ModuleWithProviders} from '@angular/core';
|
||||
|
||||
export interface MyType extends ModuleWithProviders {}
|
||||
|
||||
declare class RouterModule {
|
||||
static forRoot(): (MyType)&{ngModule:RouterModule};
|
||||
}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('imports: [[RouterModule.forRoot()]]');
|
||||
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents).toContain(`import * as i1 from 'router';`);
|
||||
expect(dtsContents)
|
||||
.toContain(
|
||||
'i0.ɵNgModuleDefWithMeta<TestModule, never, [typeof i1.RouterModule], never>');
|
||||
});
|
||||
|
||||
it('should inject special types according to the metadata', () => {
|
||||
env.tsconfig();
|
||||
env.write(`test.ts`, `
|
||||
|
@ -134,10 +134,13 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
|
||||
encapsulation: ViewEncapsulation;
|
||||
viewProviders: Provider[]|null;
|
||||
interpolation?: [string, string];
|
||||
changeDetection?: ChangeDetectionStrategy;
|
||||
}
|
||||
|
||||
export type ViewEncapsulation = number;
|
||||
|
||||
export type ChangeDetectionStrategy = number;
|
||||
|
||||
export interface R3QueryMetadataFacade {
|
||||
propertyName: string;
|
||||
first: boolean;
|
||||
|
@ -379,13 +379,9 @@ export const enum RenderFlags {
|
||||
Update = 0b10
|
||||
}
|
||||
|
||||
export const enum InitialStylingFlags {
|
||||
VALUES_MODE = 0b1,
|
||||
}
|
||||
|
||||
// Pasted from render3/interfaces/node.ts
|
||||
/**
|
||||
* A set of marker values to be used in the attributes arrays. Those markers indicate that some
|
||||
* A set of marker values to be used in the attributes arrays. These markers indicate that some
|
||||
* items are not regular attributes and the processing should be adapted accordingly.
|
||||
*/
|
||||
export const enum AttributeMarker {
|
||||
@ -396,11 +392,48 @@ export const enum AttributeMarker {
|
||||
*/
|
||||
NamespaceURI = 0,
|
||||
|
||||
/**
|
||||
* Signals class declaration.
|
||||
*
|
||||
* Each value following `Classes` designates a class name to include on the element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div class="foo bar baz">...<d/vi>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Classes, 'foo', 'bar', 'baz'];
|
||||
* ```
|
||||
*/
|
||||
Classes = 1,
|
||||
|
||||
/**
|
||||
* Signals style declaration.
|
||||
*
|
||||
* Each pair of values following `Styles` designates a style name and value to include on the
|
||||
* element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div style="width:100px; height:200px; color:red">...</div>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Styles, 'width', '100px', 'height'. '200px', 'color', 'red'];
|
||||
* ```
|
||||
*/
|
||||
Styles = 2,
|
||||
|
||||
/**
|
||||
* This marker indicates that the following attribute names were extracted from bindings (ex.:
|
||||
* [foo]="exp") and / or event handlers (ex. (bar)="doSth()").
|
||||
* Taking the above bindings and outputs as an example an attributes array could look as follows:
|
||||
* ['class', 'fade in', AttributeMarker.SelectOnly, 'foo', 'bar']
|
||||
*/
|
||||
SelectOnly = 1
|
||||
}
|
||||
SelectOnly = 3,
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||
styles: facade.styles || [],
|
||||
encapsulation: facade.encapsulation as any,
|
||||
interpolation: interpolationConfig,
|
||||
changeDetection: facade.changeDetection,
|
||||
animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null,
|
||||
viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) :
|
||||
null,
|
||||
|
@ -43,6 +43,8 @@ export class Identifiers {
|
||||
|
||||
static elementStyling: o.ExternalReference = {name: 'ɵelementStyling', moduleName: CORE};
|
||||
|
||||
static elementHostAttrs: o.ExternalReference = {name: 'ɵelementHostAttrs', moduleName: CORE};
|
||||
|
||||
static elementStylingMap: o.ExternalReference = {name: 'ɵelementStylingMap', moduleName: CORE};
|
||||
|
||||
static elementStyleProp: o.ExternalReference = {name: 'ɵelementStyleProp', moduleName: CORE};
|
||||
|
@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ViewEncapsulation} from '../../core';
|
||||
import {ChangeDetectionStrategy, ViewEncapsulation} from '../../core';
|
||||
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import * as t from '../r3_ast';
|
||||
import {R3DependencyMetadata} from '../r3_factory';
|
||||
|
||||
|
||||
/**
|
||||
* Information needed to compile a directive for the render3 runtime.
|
||||
*/
|
||||
@ -184,14 +185,19 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
|
||||
|
||||
/**
|
||||
* Whether translation variable name should contain external message id
|
||||
* (used by Closure Compiler's output of `goog.getMsg` for transition period)
|
||||
* (used by Closure Compiler's output of `goog.getMsg` for transition period).
|
||||
*/
|
||||
i18nUseExternalIds: boolean;
|
||||
|
||||
/**
|
||||
* Overrides the default interpolation start and end delimiters ({{ and }})
|
||||
* Overrides the default interpolation start and end delimiters ({{ and }}).
|
||||
*/
|
||||
interpolation: InterpolationConfig;
|
||||
|
||||
/**
|
||||
* Strategy used for detecting changes in the component.
|
||||
*/
|
||||
changeDetection?: ChangeDetectionStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ import {Render3ParseResult} from '../r3_template_transform';
|
||||
import {typeWithParameters} from '../util';
|
||||
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
||||
import {StylingBuilder, StylingInstruction} from './styling';
|
||||
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
||||
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
|
||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
||||
|
||||
@ -251,6 +251,7 @@ export function compileComponentFromMetadata(
|
||||
|
||||
const directivesUsed = new Set<o.Expression>();
|
||||
const pipesUsed = new Set<o.Expression>();
|
||||
const changeDetection = meta.changeDetection;
|
||||
|
||||
const template = meta.template;
|
||||
const templateBuilder = new TemplateDefinitionBuilder(
|
||||
@ -313,6 +314,11 @@ export function compileComponentFromMetadata(
|
||||
'data', o.literalMap([{key: 'animation', value: meta.animations, quoted: false}]));
|
||||
}
|
||||
|
||||
// Only set the change detection flag if it's defined and it's not the default.
|
||||
if (changeDetection != null && changeDetection !== core.ChangeDetectionStrategy.Default) {
|
||||
definitionMap.set('changeDetection', o.literal(changeDetection));
|
||||
}
|
||||
|
||||
// On the type side, remove newlines from the selector as it will need to fit into a TypeScript
|
||||
// string literal, which must be on one line.
|
||||
const selectorForType = (meta.selector || '').replace(/\n/g, '');
|
||||
@ -703,16 +709,35 @@ function createHostBindingsFunction(
|
||||
}
|
||||
}
|
||||
|
||||
if (styleBuilder.hasBindingsOrInitialValues) {
|
||||
const createInstruction = styleBuilder.buildCreateLevelInstruction(null, constantPool);
|
||||
if (createInstruction) {
|
||||
const createStmt = createStylingStmt(createInstruction, bindingContext, bindingFn);
|
||||
createStatements.push(createStmt);
|
||||
if (styleBuilder.hasBindingsOrInitialValues()) {
|
||||
// since we're dealing with directives here and directives have a hostBinding
|
||||
// function, we need to generate special instructions that deal with styling
|
||||
// (both bindings and initial values). The instruction below will instruct
|
||||
// all initial styling (styling that is inside of a host binding within a
|
||||
// directive) to be attached to the host element of the directive.
|
||||
const hostAttrsInstruction =
|
||||
styleBuilder.buildDirectiveHostAttrsInstruction(null, constantPool);
|
||||
if (hostAttrsInstruction) {
|
||||
createStatements.push(createStylingStmt(hostAttrsInstruction, bindingContext, bindingFn));
|
||||
}
|
||||
|
||||
// singular style/class bindings (things like `[style.prop]` and `[class.name]`)
|
||||
// MUST be registered on a given element within the component/directive
|
||||
// templateFn/hostBindingsFn functions. The instruction below will figure out
|
||||
// what all the bindings are and then generate the statements required to register
|
||||
// those bindings to the element via `elementStyling`.
|
||||
const elementStylingInstruction =
|
||||
styleBuilder.buildElementStylingInstruction(null, constantPool);
|
||||
if (elementStylingInstruction) {
|
||||
createStatements.push(
|
||||
createStylingStmt(elementStylingInstruction, bindingContext, bindingFn));
|
||||
}
|
||||
|
||||
// finally each binding that was registered in the statement above will need to be added to
|
||||
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
|
||||
// are evaluated and updated for the element.
|
||||
styleBuilder.buildUpdateLevelInstructions(valueConverter).forEach(instruction => {
|
||||
const updateStmt = createStylingStmt(instruction, bindingContext, bindingFn);
|
||||
updateStatements.push(updateStmt);
|
||||
updateStatements.push(createStylingStmt(instruction, bindingContext, bindingFn));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,15 @@ const enum Char {
|
||||
*
|
||||
* @param value string representation of style as used in the `style` attribute in HTML.
|
||||
* Example: `color: red; height: auto`.
|
||||
* @returns an object literal. `{ color: 'red', height: 'auto'}`.
|
||||
* @returns An array of style property name and value pairs, e.g. `['color', 'red', 'height',
|
||||
* 'auto']`
|
||||
*/
|
||||
export function parse(value: string): {[key: string]: any} {
|
||||
const styles: {[key: string]: any} = {};
|
||||
export function parse(value: string): string[] {
|
||||
// we use a string array here instead of a string map
|
||||
// because a string-map is not guaranteed to retain the
|
||||
// order of the entries whereas a string array can be
|
||||
// construted in a [key, value, key, value] format.
|
||||
const styles: string[] = [];
|
||||
|
||||
let i = 0;
|
||||
let parenDepth = 0;
|
||||
@ -72,7 +77,7 @@ export function parse(value: string): {[key: string]: any} {
|
||||
case Char.Semicolon:
|
||||
if (currentProp && valueStart > 0 && parenDepth === 0 && quote === Char.QuoteNone) {
|
||||
const styleVal = value.substring(valueStart, i - 1).trim();
|
||||
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal;
|
||||
styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
|
||||
propStart = i;
|
||||
valueStart = 0;
|
||||
currentProp = null;
|
||||
@ -84,7 +89,7 @@ export function parse(value: string): {[key: string]: any} {
|
||||
|
||||
if (currentProp && valueStart) {
|
||||
const styleVal = value.substr(valueStart).trim();
|
||||
styles[currentProp] = valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal;
|
||||
styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
|
||||
}
|
||||
|
||||
return styles;
|
||||
|
@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool} from '../../constant_pool';
|
||||
import {InitialStylingFlags} from '../../core';
|
||||
import {AST, BindingType, ParseSpan} from '../../expression_parser/ast';
|
||||
import {AttributeMarker} from '../../core';
|
||||
import {AST, BindingType} from '../../expression_parser/ast';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import * as t from '../r3_ast';
|
||||
@ -40,6 +40,10 @@ interface BoundStylingEntry {
|
||||
/**
|
||||
* Produces creation/update instructions for all styling bindings (class and style)
|
||||
*
|
||||
* It also produces the creation instruction to register all initial styling values
|
||||
* (which are all the static class="..." and style="..." attribute values that exist
|
||||
* on an element within a template).
|
||||
*
|
||||
* The builder class below handles producing instructions for the following cases:
|
||||
*
|
||||
* - Static style/class attributes (style="..." and class="...")
|
||||
@ -63,25 +67,57 @@ interface BoundStylingEntry {
|
||||
* The creation/update methods within the builder class produce these instructions.
|
||||
*/
|
||||
export class StylingBuilder {
|
||||
public readonly hasBindingsOrInitialValues = false;
|
||||
/** Whether or not there are any static styling values present */
|
||||
private _hasInitialValues = false;
|
||||
/**
|
||||
* Whether or not there are any styling bindings present
|
||||
* (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`)
|
||||
*/
|
||||
private _hasBindings = false;
|
||||
|
||||
/** the input for [class] (if it exists) */
|
||||
private _classMapInput: BoundStylingEntry|null = null;
|
||||
/** the input for [style] (if it exists) */
|
||||
private _styleMapInput: BoundStylingEntry|null = null;
|
||||
/** an array of each [style.prop] input */
|
||||
private _singleStyleInputs: BoundStylingEntry[]|null = null;
|
||||
/** an array of each [class.name] input */
|
||||
private _singleClassInputs: BoundStylingEntry[]|null = null;
|
||||
private _lastStylingInput: BoundStylingEntry|null = null;
|
||||
|
||||
// maps are used instead of hash maps because a Map will
|
||||
// retain the ordering of the keys
|
||||
|
||||
/**
|
||||
* Represents the location of each style binding in the template
|
||||
* (e.g. `<div [style.width]="w" [style.height]="h">` implies
|
||||
* that `width=0` and `height=1`)
|
||||
*/
|
||||
private _stylesIndex = new Map<string, number>();
|
||||
|
||||
/**
|
||||
* Represents the location of each class binding in the template
|
||||
* (e.g. `<div [class.big]="b" [class.hidden]="h">` implies
|
||||
* that `big=0` and `hidden=1`)
|
||||
*/
|
||||
private _classesIndex = new Map<string, number>();
|
||||
private _initialStyleValues: {[propName: string]: string} = {};
|
||||
private _initialClassValues: {[className: string]: boolean} = {};
|
||||
private _initialStyleValues: string[] = [];
|
||||
private _initialClassValues: string[] = [];
|
||||
|
||||
// certain style properties ALWAYS need sanitization
|
||||
// this is checked each time new styles are encountered
|
||||
private _useDefaultSanitizer = false;
|
||||
private _applyFnRequired = false;
|
||||
|
||||
constructor(private _elementIndexExpr: o.Expression, private _directiveExpr: o.Expression|null) {}
|
||||
|
||||
hasBindingsOrInitialValues() { return this._hasBindings || this._hasInitialValues; }
|
||||
|
||||
/**
|
||||
* Registers a given input to the styling builder to be later used when producing AOT code.
|
||||
*
|
||||
* The code below will only accept the input if it is somehow tied to styling (whether it be
|
||||
* style/class bindings or static style/class attributes).
|
||||
*/
|
||||
registerBoundInput(input: t.BoundAttribute): boolean {
|
||||
// [attr.style] or [attr.class] are skipped in the code below,
|
||||
// they should not be treated as styling-based bindings since
|
||||
@ -117,14 +153,12 @@ export class StylingBuilder {
|
||||
(this._singleStyleInputs = this._singleStyleInputs || []).push(entry);
|
||||
this._useDefaultSanitizer = this._useDefaultSanitizer || isStyleSanitizable(propertyName);
|
||||
registerIntoMap(this._stylesIndex, propertyName);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
} else {
|
||||
this._useDefaultSanitizer = true;
|
||||
this._styleMapInput = entry;
|
||||
}
|
||||
this._lastStylingInput = entry;
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
this._applyFnRequired = true;
|
||||
this._hasBindings = true;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@ -133,107 +167,152 @@ export class StylingBuilder {
|
||||
const entry = { name: className, value, sourceSpan } as BoundStylingEntry;
|
||||
if (className) {
|
||||
(this._singleClassInputs = this._singleClassInputs || []).push(entry);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
registerIntoMap(this._classesIndex, className);
|
||||
} else {
|
||||
this._classMapInput = entry;
|
||||
}
|
||||
this._lastStylingInput = entry;
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
this._applyFnRequired = true;
|
||||
this._hasBindings = true;
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the element's static style string value to the builder.
|
||||
*
|
||||
* @param value the style string (e.g. `width:100px; height:200px;`)
|
||||
*/
|
||||
registerStyleAttr(value: string) {
|
||||
this._initialStyleValues = parseStyle(value);
|
||||
Object.keys(this._initialStyleValues).forEach(prop => {
|
||||
registerIntoMap(this._stylesIndex, prop);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
});
|
||||
this._hasInitialValues = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the element's static class string value to the builder.
|
||||
*
|
||||
* @param value the className string (e.g. `disabled gold zoom`)
|
||||
*/
|
||||
registerClassAttr(value: string) {
|
||||
this._initialClassValues = {};
|
||||
value.split(/\s+/g).forEach(className => {
|
||||
this._initialClassValues[className] = true;
|
||||
registerIntoMap(this._classesIndex, className);
|
||||
(this as any).hasBindingsOrInitialValues = true;
|
||||
});
|
||||
this._initialClassValues = value.trim().split(/\s+/g);
|
||||
this._hasInitialValues = true;
|
||||
}
|
||||
|
||||
private _buildInitExpr(registry: Map<string, number>, initialValues: {[key: string]: any}):
|
||||
o.Expression|null {
|
||||
const exprs: o.Expression[] = [];
|
||||
const nameAndValueExprs: o.Expression[] = [];
|
||||
|
||||
// _c0 = [prop, prop2, prop3, ...]
|
||||
registry.forEach((value, key) => {
|
||||
const keyLiteral = o.literal(key);
|
||||
exprs.push(keyLiteral);
|
||||
const initialValue = initialValues[key];
|
||||
if (initialValue) {
|
||||
nameAndValueExprs.push(keyLiteral, o.literal(initialValue));
|
||||
/**
|
||||
* Appends all styling-related expressions to the provided attrs array.
|
||||
*
|
||||
* @param attrs an existing array where each of the styling expressions
|
||||
* will be inserted into.
|
||||
*/
|
||||
populateInitialStylingAttrs(attrs: o.Expression[]): void {
|
||||
// [CLASS_MARKER, 'foo', 'bar', 'baz' ...]
|
||||
if (this._initialClassValues.length) {
|
||||
attrs.push(o.literal(AttributeMarker.Classes));
|
||||
for (let i = 0; i < this._initialClassValues.length; i++) {
|
||||
attrs.push(o.literal(this._initialClassValues[i]));
|
||||
}
|
||||
});
|
||||
|
||||
if (nameAndValueExprs.length) {
|
||||
// _c0 = [... MARKER ...]
|
||||
exprs.push(o.literal(InitialStylingFlags.VALUES_MODE));
|
||||
// _c0 = [prop, VALUE, prop2, VALUE2, ...]
|
||||
exprs.push(...nameAndValueExprs);
|
||||
}
|
||||
|
||||
return exprs.length ? o.literalArr(exprs) : null;
|
||||
// [STYLE_MARKER, 'width', '200px', 'height', '100px', ...]
|
||||
if (this._initialStyleValues.length) {
|
||||
attrs.push(o.literal(AttributeMarker.Styles));
|
||||
for (let i = 0; i < this._initialStyleValues.length; i += 2) {
|
||||
attrs.push(
|
||||
o.literal(this._initialStyleValues[i]), o.literal(this._initialStyleValues[i + 1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildCreateLevelInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `elementHostAttrs`.
|
||||
*
|
||||
* The instruction generation code below is used for producing the AOT statement code which is
|
||||
* responsible for registering initial styles (within a directive hostBindings' creation block)
|
||||
* to the directive host element.
|
||||
*/
|
||||
buildDirectiveHostAttrsInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
StylingInstruction|null {
|
||||
if (this.hasBindingsOrInitialValues) {
|
||||
const initialClasses = this._buildInitExpr(this._classesIndex, this._initialClassValues);
|
||||
const initialStyles = this._buildInitExpr(this._stylesIndex, this._initialStyleValues);
|
||||
|
||||
// in the event that a [style] binding is used then sanitization will
|
||||
// always be imported because it is not possible to know ahead of time
|
||||
// whether style bindings will use or not use any sanitizable properties
|
||||
// that isStyleSanitizable() will detect
|
||||
const useSanitizer = this._useDefaultSanitizer;
|
||||
const params: (o.Expression)[] = [];
|
||||
|
||||
if (initialClasses) {
|
||||
// the template compiler handles initial class styling (e.g. class="foo") values
|
||||
// in a special command called `elementClass` so that the initial class
|
||||
// can be processed during runtime. These initial class values are bound to
|
||||
// a constant because the inital class values do not change (since they're static).
|
||||
params.push(constantPool.getConstLiteral(initialClasses, true));
|
||||
} else if (initialStyles || useSanitizer || this._directiveExpr) {
|
||||
// no point in having an extra `null` value unless there are follow-up params
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
|
||||
if (initialStyles) {
|
||||
// the template compiler handles initial style (e.g. style="foo") values
|
||||
// in a special command called `elementStyle` so that the initial styles
|
||||
// can be processed during runtime. These initial styles values are bound to
|
||||
// a constant because the inital style values do not change (since they're static).
|
||||
params.push(constantPool.getConstLiteral(initialStyles, true));
|
||||
} else if (useSanitizer || this._directiveExpr) {
|
||||
// no point in having an extra `null` value unless there are follow-up params
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
|
||||
if (useSanitizer || this._directiveExpr) {
|
||||
params.push(useSanitizer ? o.importExpr(R3.defaultStyleSanitizer) : o.NULL_EXPR);
|
||||
if (this._directiveExpr) {
|
||||
params.push(this._directiveExpr);
|
||||
if (this._hasInitialValues && this._directiveExpr) {
|
||||
return {
|
||||
sourceSpan,
|
||||
reference: R3.elementHostAttrs,
|
||||
buildParams: () => {
|
||||
const attrs: o.Expression[] = [];
|
||||
this.populateInitialStylingAttrs(attrs);
|
||||
return [this._directiveExpr !, getConstantLiteralFromArray(constantPool, attrs)];
|
||||
}
|
||||
}
|
||||
|
||||
return {sourceSpan, reference: R3.elementStyling, buildParams: () => params};
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _buildStylingMap(valueConverter: ValueConverter): StylingInstruction|null {
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `elementStyling`.
|
||||
*
|
||||
* The instruction generation code below is used for producing the AOT statement code which is
|
||||
* responsible for registering style/class bindings to an element.
|
||||
*/
|
||||
buildElementStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
StylingInstruction|null {
|
||||
if (this._hasBindings) {
|
||||
return {
|
||||
sourceSpan,
|
||||
reference: R3.elementStyling,
|
||||
buildParams: () => {
|
||||
// a string array of every style-based binding
|
||||
const styleBindingProps =
|
||||
this._singleStyleInputs ? this._singleStyleInputs.map(i => o.literal(i.name)) : [];
|
||||
// a string array of every class-based binding
|
||||
const classBindingNames =
|
||||
this._singleClassInputs ? this._singleClassInputs.map(i => o.literal(i.name)) : [];
|
||||
|
||||
// to salvage space in the AOT generated code, there is no point in passing
|
||||
// in `null` into a param if any follow-up params are not used. Therefore,
|
||||
// only when a trailing param is used then it will be filled with nulls in between
|
||||
// (otherwise a shorter amount of params will be filled). The code below helps
|
||||
// determine how many params are required in the expression code.
|
||||
//
|
||||
// min params => elementStyling()
|
||||
// max params => elementStyling(classBindings, styleBindings, sanitizer, directive)
|
||||
let expectedNumberOfArgs = 0;
|
||||
if (this._directiveExpr) {
|
||||
expectedNumberOfArgs = 4;
|
||||
} else if (this._useDefaultSanitizer) {
|
||||
expectedNumberOfArgs = 3;
|
||||
} else if (styleBindingProps.length) {
|
||||
expectedNumberOfArgs = 2;
|
||||
} else if (classBindingNames.length) {
|
||||
expectedNumberOfArgs = 1;
|
||||
}
|
||||
|
||||
const params: o.Expression[] = [];
|
||||
addParam(
|
||||
params, classBindingNames.length > 0,
|
||||
getConstantLiteralFromArray(constantPool, classBindingNames), 1,
|
||||
expectedNumberOfArgs);
|
||||
addParam(
|
||||
params, styleBindingProps.length > 0,
|
||||
getConstantLiteralFromArray(constantPool, styleBindingProps), 2,
|
||||
expectedNumberOfArgs);
|
||||
addParam(
|
||||
params, this._useDefaultSanitizer, o.importExpr(R3.defaultStyleSanitizer), 3,
|
||||
expectedNumberOfArgs);
|
||||
if (this._directiveExpr) {
|
||||
params.push(this._directiveExpr);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instruction with all the expressions and parameters for `elementStylingMap`.
|
||||
*
|
||||
* The instruction data will contain all expressions for `elementStylingMap` to function
|
||||
* which include the `[style]` and `[class]` expression params (if they exist) as well as
|
||||
* the sanitizer and directive reference expression.
|
||||
*/
|
||||
buildElementStylingMapInstruction(valueConverter: ValueConverter): StylingInstruction|null {
|
||||
if (this._classMapInput || this._styleMapInput) {
|
||||
const stylingInput = this._classMapInput ! || this._styleMapInput !;
|
||||
|
||||
@ -332,18 +411,20 @@ export class StylingBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs all instructions which contain the expressions that will be placed
|
||||
* into the update block of a template function or a directive hostBindings function.
|
||||
*/
|
||||
buildUpdateLevelInstructions(valueConverter: ValueConverter) {
|
||||
const instructions: StylingInstruction[] = [];
|
||||
if (this.hasBindingsOrInitialValues) {
|
||||
const mapInstruction = this._buildStylingMap(valueConverter);
|
||||
if (this._hasBindings) {
|
||||
const mapInstruction = this.buildElementStylingMapInstruction(valueConverter);
|
||||
if (mapInstruction) {
|
||||
instructions.push(mapInstruction);
|
||||
}
|
||||
instructions.push(...this._buildStyleInputs(valueConverter));
|
||||
instructions.push(...this._buildClassInputs(valueConverter));
|
||||
if (this._applyFnRequired) {
|
||||
instructions.push(this._buildApplyFn());
|
||||
}
|
||||
instructions.push(this._buildApplyFn());
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
@ -363,3 +444,26 @@ function isStyleSanitizable(prop: string): boolean {
|
||||
return prop === 'background-image' || prop === 'background' || prop === 'border-image' ||
|
||||
prop === 'filter' || prop === 'list-style' || prop === 'list-style-image';
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper function to either provide the constant literal that will house the value
|
||||
* here or a null value if the provided values are empty.
|
||||
*/
|
||||
function getConstantLiteralFromArray(
|
||||
constantPool: ConstantPool, values: o.Expression[]): o.Expression {
|
||||
return values.length ? constantPool.getConstLiteral(o.literalArr(values), true) : o.NULL_EXPR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper function that adds a parameter or does nothing at all depending on the provided
|
||||
* predicate and totalExpectedArgs values
|
||||
*/
|
||||
function addParam(
|
||||
params: o.Expression[], predicate: boolean, value: o.Expression, argNumber: number,
|
||||
totalExpectedArgs: number) {
|
||||
if (predicate) {
|
||||
params.push(value);
|
||||
} else if (argNumber < totalExpectedArgs) {
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ import {I18nContext} from './i18n/context';
|
||||
import {I18nMetaVisitor} from './i18n/meta';
|
||||
import {getSerializedI18nContent} from './i18n/serializer';
|
||||
import {I18N_ICU_MAPPING_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util';
|
||||
import {StylingBuilder, StylingInstruction} from './styling';
|
||||
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
||||
import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util';
|
||||
|
||||
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
|
||||
@ -114,6 +114,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
// Selectors found in the <ng-content> tags in the template.
|
||||
private _ngContentSelectors: string[] = [];
|
||||
|
||||
// Number of non-default selectors found in all parent templates of this template. We need to
|
||||
// track it to properly adjust projection bucket index in the `projection` instruction.
|
||||
private _ngContentSelectorsOffset = 0;
|
||||
|
||||
constructor(
|
||||
private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0,
|
||||
private contextName: string|null, private i18nContext: I18nContext|null,
|
||||
@ -166,7 +170,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
});
|
||||
}
|
||||
|
||||
buildTemplateFunction(nodes: t.Node[], variables: t.Variable[], i18n?: i18n.AST): o.FunctionExpr {
|
||||
buildTemplateFunction(
|
||||
nodes: t.Node[], variables: t.Variable[], ngContentSelectorsOffset: number = 0,
|
||||
i18n?: i18n.AST): o.FunctionExpr {
|
||||
this._ngContentSelectorsOffset = ngContentSelectorsOffset;
|
||||
|
||||
if (this._namespace !== R3.namespaceHTML) {
|
||||
this.creationInstruction(null, this._namespace);
|
||||
}
|
||||
@ -192,8 +200,23 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
// resolving bindings. We also count bindings in this pass as we walk bound expressions.
|
||||
t.visitAll(this, nodes);
|
||||
|
||||
// Output a `ProjectionDef` instruction when some `<ng-content>` are present
|
||||
if (this._hasNgContent) {
|
||||
// Add total binding count to pure function count so pure function instructions are
|
||||
// generated with the correct slot offset when update instructions are processed.
|
||||
this._pureFunctionSlots += this._bindingSlots;
|
||||
|
||||
// Pipes are walked in the first pass (to enqueue `pipe()` creation instructions and
|
||||
// `pipeBind` update instructions), so we have to update the slot offsets manually
|
||||
// to account for bindings.
|
||||
this._valueConverter.updatePipeSlotOffsets(this._bindingSlots);
|
||||
|
||||
// Nested templates must be processed before creation instructions so template()
|
||||
// instructions can be generated with the correct internal const count.
|
||||
this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn());
|
||||
|
||||
// Output the `projectionDef` instruction when some `<ng-content>` are present.
|
||||
// The `projectionDef` instruction only emitted for the component template and it is skipped for
|
||||
// nested templates (<ng-template> tags).
|
||||
if (this.level === 0 && this._hasNgContent) {
|
||||
const parameters: o.Expression[] = [];
|
||||
|
||||
// Only selectors with a non-default value are generated
|
||||
@ -212,19 +235,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
this.creationInstruction(null, R3.projectionDef, parameters, /* prepend */ true);
|
||||
}
|
||||
|
||||
// Add total binding count to pure function count so pure function instructions are
|
||||
// generated with the correct slot offset when update instructions are processed.
|
||||
this._pureFunctionSlots += this._bindingSlots;
|
||||
|
||||
// Pipes are walked in the first pass (to enqueue `pipe()` creation instructions and
|
||||
// `pipeBind` update instructions), so we have to update the slot offsets manually
|
||||
// to account for bindings.
|
||||
this._valueConverter.updatePipeSlotOffsets(this._bindingSlots);
|
||||
|
||||
// Nested templates must be processed before creation instructions so template()
|
||||
// instructions can be generated with the correct internal const count.
|
||||
this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn());
|
||||
|
||||
if (initI18nContext) {
|
||||
this.i18nEnd(null, selfClosingI18nInstruction);
|
||||
}
|
||||
@ -419,7 +429,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
const slot = this.allocateDataSlot();
|
||||
let selectorIndex = ngContent.selector === DEFAULT_NG_CONTENT_SELECTOR ?
|
||||
0 :
|
||||
this._ngContentSelectors.push(ngContent.selector);
|
||||
this._ngContentSelectors.push(ngContent.selector) + this._ngContentSelectorsOffset;
|
||||
const parameters: o.Expression[] = [o.literal(slot)];
|
||||
|
||||
const attributeAsList: string[] = [];
|
||||
@ -522,7 +532,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
|
||||
// this will build the instructions so that they fall into the following syntax
|
||||
// add attributes for directive matching purposes
|
||||
attributes.push(...this.prepareSyntheticAndSelectOnlyAttrs(allOtherInputs, element.outputs));
|
||||
attributes.push(...this.prepareSyntheticAndSelectOnlyAttrs(
|
||||
allOtherInputs, element.outputs, stylingBuilder));
|
||||
parameters.push(this.toAttrsParam(attributes));
|
||||
|
||||
// local refs (ex.: <div #foo #bar="baz">)
|
||||
@ -552,11 +563,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
return element.children.length > 0;
|
||||
};
|
||||
|
||||
const createSelfClosingInstruction = !stylingBuilder.hasBindingsOrInitialValues &&
|
||||
const createSelfClosingInstruction = !stylingBuilder.hasBindingsOrInitialValues() &&
|
||||
!isNgContainer && element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren();
|
||||
|
||||
const createSelfClosingI18nInstruction = !createSelfClosingInstruction &&
|
||||
!stylingBuilder.hasBindingsOrInitialValues && hasTextChildrenOnly(element.children);
|
||||
!stylingBuilder.hasBindingsOrInitialValues() && hasTextChildrenOnly(element.children);
|
||||
|
||||
if (createSelfClosingInstruction) {
|
||||
this.creationInstruction(element.sourceSpan, R3.element, trimTrailingNulls(parameters));
|
||||
@ -606,10 +617,16 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
}
|
||||
}
|
||||
|
||||
// initial styling for static style="..." and class="..." attributes
|
||||
// The style bindings code is placed into two distinct blocks within the template function AOT
|
||||
// code: creation and update. The creation code contains the `elementStyling` instructions
|
||||
// which will apply the collected binding values to the element. `elementStyling` is
|
||||
// designed to run inside of `elementStart` and `elementEnd`. The update instructions
|
||||
// (things like `elementStyleProp`, `elementClassProp`, etc..) are applied later on in this
|
||||
// file
|
||||
this.processStylingInstruction(
|
||||
implicit,
|
||||
stylingBuilder.buildCreateLevelInstruction(element.sourceSpan, this.constantPool), true);
|
||||
stylingBuilder.buildElementStylingInstruction(element.sourceSpan, this.constantPool),
|
||||
true);
|
||||
|
||||
// Generate Listeners (outputs)
|
||||
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
||||
@ -619,6 +636,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
});
|
||||
}
|
||||
|
||||
// the code here will collect all update-level styling instructions and add them to the
|
||||
// update block of the template function AOT code. Instructions like `elementStyleProp`,
|
||||
// `elementStylingMap`, `elementClassProp` and `elementStylingApply` are all generated
|
||||
// and assign in the code below.
|
||||
stylingBuilder.buildUpdateLevelInstructions(this._valueConverter).forEach(instruction => {
|
||||
this.processStylingInstruction(implicit, instruction, false);
|
||||
});
|
||||
@ -738,8 +759,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
// template definition. e.g. <div *ngIf="showing"> {{ foo }} </div> <div #foo></div>
|
||||
this._nestedTemplateFns.push(() => {
|
||||
const templateFunctionExpr = templateVisitor.buildTemplateFunction(
|
||||
template.children, template.variables, template.i18n);
|
||||
template.children, template.variables,
|
||||
this._ngContentSelectors.length + this._ngContentSelectorsOffset, template.i18n);
|
||||
this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||
if (templateVisitor._hasNgContent) {
|
||||
this._hasNgContent = true;
|
||||
this._ngContentSelectors.push(...templateVisitor._ngContentSelectors);
|
||||
}
|
||||
});
|
||||
|
||||
// e.g. template(1, MyComp_Template_1)
|
||||
@ -919,8 +945,26 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
}
|
||||
}
|
||||
|
||||
private prepareSyntheticAndSelectOnlyAttrs(inputs: t.BoundAttribute[], outputs: t.BoundEvent[]):
|
||||
o.Expression[] {
|
||||
/**
|
||||
* Prepares all attribute expression values for the `TAttributes` array.
|
||||
*
|
||||
* The purpose of this function is to properly construct an attributes array that
|
||||
* is passed into the `elementStart` (or just `element`) functions. Because there
|
||||
* are many different types of attributes, the array needs to be constructed in a
|
||||
* special way so that `elementStart` can properly evaluate them.
|
||||
*
|
||||
* The format looks like this:
|
||||
*
|
||||
* ```
|
||||
* attrs = [prop, value, prop2, value2,
|
||||
* CLASSES, class1, class2,
|
||||
* STYLES, style1, value1, style2, value2,
|
||||
* SELECT_ONLY, name1, name2, name2, ...]
|
||||
* ```
|
||||
*/
|
||||
private prepareSyntheticAndSelectOnlyAttrs(
|
||||
inputs: t.BoundAttribute[], outputs: t.BoundEvent[],
|
||||
styles?: StylingBuilder): o.Expression[] {
|
||||
const attrExprs: o.Expression[] = [];
|
||||
const nonSyntheticInputs: t.BoundAttribute[] = [];
|
||||
|
||||
@ -939,6 +983,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
});
|
||||
}
|
||||
|
||||
// it's important that this occurs before SelectOnly because once `elementStart`
|
||||
// comes across the SelectOnly marker then it will continue reading each value as
|
||||
// as single property value cell by cell.
|
||||
if (styles) {
|
||||
styles.populateInitialStylingAttrs(attrExprs);
|
||||
}
|
||||
|
||||
if (nonSyntheticInputs.length || outputs.length) {
|
||||
attrExprs.push(o.literal(core.AttributeMarker.SelectOnly));
|
||||
nonSyntheticInputs.forEach((i: t.BoundAttribute) => attrExprs.push(asLiteral(i.name)));
|
||||
|
@ -53,7 +53,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
function compileApp(): GeneratedFile {
|
||||
const {genFiles} = compile([rootDir, angularFiles]);
|
||||
return genFiles.find(
|
||||
genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'));
|
||||
genFile => genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts')) !;
|
||||
}
|
||||
|
||||
function findLineAndColumn(
|
||||
@ -321,7 +321,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const genFilePreamble = '/* Hello world! */';
|
||||
const {genFiles} = compile([FILES, angularFiles]);
|
||||
const genFile =
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')) !;
|
||||
const genSource = toTypeScript(genFile, genFilePreamble);
|
||||
expect(genSource.startsWith(genFilePreamble)).toBe(true);
|
||||
});
|
||||
@ -440,7 +440,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
}
|
||||
};
|
||||
const {genFiles} = compile([FILES, angularFiles]);
|
||||
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
|
||||
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
const createComponentFactoryCall = /ɵccf\([^)]*\)/m.exec(genSource) ![0].replace(/\s*/g, '');
|
||||
// selector
|
||||
@ -471,7 +471,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
};
|
||||
const {genFiles} = compile([FILES, angularFiles]);
|
||||
const genFile =
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts')) !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
expect(genSource).not.toContain('check(');
|
||||
|
||||
@ -510,7 +510,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
|
||||
const {genFiles: appGenFiles} =
|
||||
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
|
||||
const appNgFactoryTs = toTypeScript(appNgFactory);
|
||||
expect(appNgFactoryTs).not.toContain('AType');
|
||||
expect(appNgFactoryTs).toContain('AValue');
|
||||
@ -557,7 +557,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
|
||||
const {genFiles: appGenFiles} =
|
||||
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
|
||||
const appNgFactoryTs = toTypeScript(appNgFactory);
|
||||
|
||||
// metadata of ctor calls is preserved, so we reexport the argument
|
||||
@ -598,7 +598,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
|
||||
const {genFiles: appGenFiles} =
|
||||
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
|
||||
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts') !;
|
||||
const appNgFactoryTs = toTypeScript(appNgFactory);
|
||||
|
||||
// we don't need to reexport exported symbols via the .ngfactory
|
||||
@ -690,7 +690,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
compile([libInput, getAngularSummaryFiles()], {useSummaries: true});
|
||||
const {genFiles} =
|
||||
compile([libOutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts') !;
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(toTypeScript(mainNgFactory))
|
||||
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam]`);
|
||||
@ -743,7 +743,7 @@ describe('compiler (unbundled Angular)', () => {
|
||||
compile([lib1OutDir, lib2Input, getAngularSummaryFiles()], {useSummaries: true});
|
||||
const {genFiles} =
|
||||
compile([lib2OutDir, appInput, getAngularSummaryFiles()], {useSummaries: true});
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
|
||||
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts') !;
|
||||
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
|
||||
expect(toTypeScript(mainNgFactory))
|
||||
.toContain(`${flags},(null as any),0,i1.Extends,[i2.AParam_2]`);
|
||||
|
@ -41,7 +41,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -70,7 +70,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -99,7 +99,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -125,7 +125,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toContain(`import * as i0 from '/app/app.module'`);
|
||||
@ -164,7 +164,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
|
||||
@ -198,7 +198,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(/useClass:\{\s*reference:i1.MyService/);
|
||||
@ -225,7 +225,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(
|
||||
@ -247,7 +247,7 @@ describe('aot summaries for jit', () => {
|
||||
const rootDir = {'app': appDir};
|
||||
|
||||
const genFile =
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts');
|
||||
compileApp(rootDir).genFiles.find(f => f.genFileUrl === '/app/app.module.ngsummary.ts') !;
|
||||
const genSource = toTypeScript(genFile);
|
||||
|
||||
expect(genSource).toMatch(
|
||||
@ -294,9 +294,10 @@ describe('aot summaries for jit', () => {
|
||||
};
|
||||
const {outDir: lib3In, genFiles: lib2Gen} = compileApp(lib2In, {useSummaries: true});
|
||||
|
||||
const lib2ModuleNgSummary = lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts');
|
||||
const lib2ModuleNgSummary =
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/module.ngsummary.ts') !;
|
||||
const lib2ReexportNgSummary =
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts');
|
||||
lib2Gen.find(f => f.genFileUrl === '/lib2/reexport.ngsummary.ts') !;
|
||||
|
||||
// ngsummaries should add reexports for imported NgModules from a direct dependency
|
||||
expect(toTypeScript(lib2ModuleNgSummary))
|
||||
@ -325,9 +326,10 @@ describe('aot summaries for jit', () => {
|
||||
};
|
||||
|
||||
const lib3Gen = compileApp(lib3In, {useSummaries: true}).genFiles;
|
||||
const lib3ModuleNgSummary = lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts');
|
||||
const lib3ModuleNgSummary =
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/module.ngsummary.ts') !;
|
||||
const lib3ReexportNgSummary =
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts');
|
||||
lib3Gen.find(f => f.genFileUrl === '/lib3/reexport.ngsummary.ts') !;
|
||||
|
||||
// ngsummary.ts files should use the reexported values from direct and deep deps
|
||||
const lib3ModuleNgSummarySource = toTypeScript(lib3ModuleNgSummary);
|
||||
|
@ -422,7 +422,7 @@ export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
}[] = []) {}
|
||||
addSummary(summary: Summary<StaticSymbol>) { this.summaries.push(summary); }
|
||||
resolveSummary(reference: StaticSymbol): Summary<StaticSymbol> {
|
||||
return this.summaries.find(summary => summary.symbol === reference);
|
||||
return this.summaries.find(summary => summary.symbol === reference) !;
|
||||
}
|
||||
getSymbolsOf(filePath: string): StaticSymbol[]|null {
|
||||
const symbols = this.summaries.filter(summary => summary.symbol.filePath === filePath)
|
||||
|
@ -10,55 +10,53 @@ import {hyphenate, parse as parseStyle, stripUnnecessaryQuotes} from '../../src/
|
||||
describe('style parsing', () => {
|
||||
it('should parse empty or blank strings', () => {
|
||||
const result1 = parseStyle('');
|
||||
expect(result1).toEqual({});
|
||||
expect(result1).toEqual([]);
|
||||
|
||||
const result2 = parseStyle(' ');
|
||||
expect(result2).toEqual({});
|
||||
expect(result2).toEqual([]);
|
||||
});
|
||||
|
||||
it('should parse a string into a key/value map', () => {
|
||||
const result = parseStyle('width:100px;height:200px;opacity:0');
|
||||
expect(result).toEqual({width: '100px', height: '200px', opacity: '0'});
|
||||
expect(result).toEqual(['width', '100px', 'height', '200px', 'opacity', '0']);
|
||||
});
|
||||
|
||||
it('should trim values and properties', () => {
|
||||
const result = parseStyle('width :333px ; height:666px ; opacity: 0.5;');
|
||||
expect(result).toEqual({width: '333px', height: '666px', opacity: '0.5'});
|
||||
expect(result).toEqual(['width', '333px', 'height', '666px', 'opacity', '0.5']);
|
||||
});
|
||||
|
||||
it('should chomp out start/end quotes', () => {
|
||||
const result = parseStyle(
|
||||
'content: "foo"; opacity: \'0.5\'; font-family: "Verdana", Helvetica, "sans-serif"');
|
||||
expect(result).toEqual(
|
||||
{content: 'foo', opacity: '0.5', 'font-family': '"Verdana", Helvetica, "sans-serif"'});
|
||||
['content', 'foo', 'opacity', '0.5', 'font-family', '"Verdana", Helvetica, "sans-serif"']);
|
||||
});
|
||||
|
||||
it('should not mess up with quoted strings that contain [:;] values', () => {
|
||||
const result = parseStyle('content: "foo; man: guy"; width: 100px');
|
||||
expect(result).toEqual({content: 'foo; man: guy', width: '100px'});
|
||||
expect(result).toEqual(['content', 'foo; man: guy', 'width', '100px']);
|
||||
});
|
||||
|
||||
it('should not mess up with quoted strings that contain inner quote values', () => {
|
||||
const quoteStr = '"one \'two\' three \"four\" five"';
|
||||
const result = parseStyle(`content: ${quoteStr}; width: 123px`);
|
||||
expect(result).toEqual({content: quoteStr, width: '123px'});
|
||||
expect(result).toEqual(['content', quoteStr, 'width', '123px']);
|
||||
});
|
||||
|
||||
it('should respect parenthesis that are placed within a style', () => {
|
||||
const result = parseStyle('background-image: url("foo.jpg")');
|
||||
expect(result).toEqual({'background-image': 'url("foo.jpg")'});
|
||||
expect(result).toEqual(['background-image', 'url("foo.jpg")']);
|
||||
});
|
||||
|
||||
it('should respect multi-level parenthesis that contain special [:;] characters', () => {
|
||||
const result = parseStyle('color: rgba(calc(50 * 4), var(--cool), :5;); height: 100px;');
|
||||
expect(result).toEqual({color: 'rgba(calc(50 * 4), var(--cool), :5;)', height: '100px'});
|
||||
expect(result).toEqual(['color', 'rgba(calc(50 * 4), var(--cool), :5;)', 'height', '100px']);
|
||||
});
|
||||
|
||||
it('should hyphenate style properties from camel case', () => {
|
||||
const result = parseStyle('borderWidth: 200px');
|
||||
expect(result).toEqual({
|
||||
'border-width': '200px',
|
||||
});
|
||||
expect(result).toEqual(['border-width', '200px']);
|
||||
});
|
||||
|
||||
describe('quote chomping', () => {
|
||||
|
@ -92,6 +92,7 @@ export {
|
||||
elementContainerStart as ɵelementContainerStart,
|
||||
elementContainerEnd as ɵelementContainerEnd,
|
||||
elementStyling as ɵelementStyling,
|
||||
elementHostAttrs as ɵelementHostAttrs,
|
||||
elementStylingMap as ɵelementStylingMap,
|
||||
elementStyleProp as ɵelementStyleProp,
|
||||
elementStylingApply as ɵelementStylingApply,
|
||||
|
@ -12,7 +12,7 @@ import {getComponent, getContext, getInjectionTokens, getInjector, getListeners,
|
||||
import {TNode} from '../render3/interfaces/node';
|
||||
import {StylingIndex} from '../render3/interfaces/styling';
|
||||
import {TVIEW} from '../render3/interfaces/view';
|
||||
import {getProp, getValue, isClassBased} from '../render3/styling/class_and_style_bindings';
|
||||
import {getProp, getValue, isClassBasedValue} from '../render3/styling/class_and_style_bindings';
|
||||
import {getStylingContext} from '../render3/styling/util';
|
||||
import {DebugContext} from '../view/index';
|
||||
|
||||
@ -273,7 +273,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
||||
if (stylingContext) {
|
||||
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
|
||||
i += StylingIndex.Size) {
|
||||
if (isClassBased(lNode, i)) {
|
||||
if (isClassBasedValue(lNode, i)) {
|
||||
const className = getProp(lNode, i);
|
||||
const value = getValue(lNode, i);
|
||||
if (typeof value == 'boolean') {
|
||||
@ -303,7 +303,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
||||
if (stylingContext) {
|
||||
for (let i = StylingIndex.SingleStylesStartPosition; i < lNode.length;
|
||||
i += StylingIndex.Size) {
|
||||
if (!isClassBased(lNode, i)) {
|
||||
if (!isClassBasedValue(lNode, i)) {
|
||||
const styleName = getProp(lNode, i);
|
||||
const value = getValue(lNode, i) as string | null;
|
||||
if (value !== null) {
|
||||
|
@ -11,7 +11,7 @@ import {devModeEqual} from '../change_detection/change_detection_util';
|
||||
import {assertDataInRange, assertLessThan, assertNotEqual} from './assert';
|
||||
import {throwErrorIfNoChangesMode} from './errors';
|
||||
import {BINDING_INDEX, LView} from './interfaces/view';
|
||||
import {getCheckNoChangesMode, getCreationMode} from './state';
|
||||
import {getCheckNoChangesMode, isCreationMode} from './state';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {isDifferent} from './util';
|
||||
|
||||
@ -44,7 +44,7 @@ export function bindingUpdated(lView: LView, bindingIndex: number, value: any):
|
||||
} else if (isDifferent(lView[bindingIndex], value)) {
|
||||
if (ngDevMode && getCheckNoChangesMode()) {
|
||||
if (!devModeEqual(lView[bindingIndex], value)) {
|
||||
throwErrorIfNoChangesMode(getCreationMode(), lView[bindingIndex], value);
|
||||
throwErrorIfNoChangesMode(isCreationMode(lView), lView[bindingIndex], value);
|
||||
}
|
||||
}
|
||||
lView[bindingIndex] = value;
|
||||
|
@ -22,7 +22,7 @@ import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition'
|
||||
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {PlayerHandler} from './interfaces/player';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
|
||||
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
|
||||
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
|
||||
import {defaultScheduler, getRootView, readPatchedLView, stringify} from './util';
|
||||
|
||||
@ -133,7 +133,9 @@ export function renderComponent<T>(
|
||||
component = createRootComponent(
|
||||
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
|
||||
|
||||
refreshDescendantViews(rootView, null);
|
||||
refreshDescendantViews(rootView); // creation mode pass
|
||||
rootView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
refreshDescendantViews(rootView); // update mode pass
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
if (rendererFactory.end) rendererFactory.end();
|
||||
|
@ -208,10 +208,9 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
|
||||
|
||||
addToViewTree(rootLView, HEADER_OFFSET, componentView);
|
||||
|
||||
refreshDescendantViews(rootLView, RenderFlags.Create);
|
||||
refreshDescendantViews(rootLView);
|
||||
} finally {
|
||||
leaveView(oldLView, true);
|
||||
leaveView(oldLView);
|
||||
if (rendererFactory.end) rendererFactory.end();
|
||||
}
|
||||
|
||||
|
@ -7,12 +7,13 @@
|
||||
*/
|
||||
import './ng_dev_mode';
|
||||
import {assertDomNode} from './assert';
|
||||
import {EMPTY_ARRAY} from './definition';
|
||||
import {EMPTY_ARRAY} from './empty';
|
||||
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
|
||||
import {TNode, TNodeFlags} from './interfaces/node';
|
||||
import {RElement} from './interfaces/renderer';
|
||||
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
|
||||
import {getComponentViewByIndex, getNativeByTNode, getTNode, readElementValue, readPatchedData} from './util';
|
||||
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
|
||||
|
||||
|
||||
|
||||
/** Returns the matching `LContext` data for a given DOM node, directive or component instance.
|
||||
|
@ -9,22 +9,15 @@
|
||||
import './ng_dev_mode';
|
||||
|
||||
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||
import {Provider} from '../di/provider';
|
||||
import {NgModuleDef} from '../metadata/ng_module';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
import {Mutable, Type} from '../type';
|
||||
import {noSideEffects, stringify} from '../util';
|
||||
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields';
|
||||
import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
|
||||
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
||||
import {CssSelectorList} from './interfaces/projection';
|
||||
|
||||
export const EMPTY: {} = {};
|
||||
export const EMPTY_ARRAY: any[] = [];
|
||||
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
||||
Object.freeze(EMPTY);
|
||||
Object.freeze(EMPTY_ARRAY);
|
||||
}
|
||||
let _renderCompCount = 0;
|
||||
|
||||
/**
|
||||
@ -389,7 +382,7 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
|
||||
|
||||
*/
|
||||
function invertObject(obj: any, secondary?: any): any {
|
||||
if (obj == null) return EMPTY;
|
||||
if (obj == null) return EMPTY_OBJ;
|
||||
const newLookup: any = {};
|
||||
for (const minifiedKey in obj) {
|
||||
if (obj.hasOwnProperty(minifiedKey)) {
|
||||
|
@ -21,7 +21,8 @@ import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TN
|
||||
import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LView, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state';
|
||||
import {getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, stringify} from './util';
|
||||
import {getHostTElementNode, getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, isComponentDef, stringify} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* Defines if the call to `inject` should include `viewProviders` in its resolution.
|
||||
@ -197,7 +198,7 @@ export function getInjectorIndex(tNode: TNode, hostView: LView): number {
|
||||
*/
|
||||
export function getParentInjectorLocation(tNode: TNode, view: LView): RelativeInjectorLocation {
|
||||
if (tNode.parent && tNode.parent.injectorIndex !== -1) {
|
||||
return tNode.parent.injectorIndex as any; // ViewOffset is 0, AcrossHostBoundary is 0
|
||||
return tNode.parent.injectorIndex as any; // ViewOffset is 0
|
||||
}
|
||||
|
||||
// For most cases, the parent injector index can be found on the host node (e.g. for component
|
||||
@ -210,13 +211,9 @@ export function getParentInjectorLocation(tNode: TNode, view: LView): RelativeIn
|
||||
hostTNode = view[HOST_NODE] !;
|
||||
viewOffset++;
|
||||
}
|
||||
const acrossHostBoundary = hostTNode && hostTNode.type === TNodeType.Element ?
|
||||
RelativeInjectorLocationFlags.AcrossHostBoundary :
|
||||
0;
|
||||
|
||||
return hostTNode ?
|
||||
hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) |
|
||||
acrossHostBoundary :
|
||||
hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) :
|
||||
-1 as any;
|
||||
}
|
||||
|
||||
@ -323,6 +320,7 @@ export function getOrCreateInjectable<T>(
|
||||
let previousTView: TView|null = null;
|
||||
let injectorIndex = getInjectorIndex(tNode, lView);
|
||||
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
|
||||
let hostTElementNode: TNode|null = flags & InjectFlags.Host ? getHostTElementNode(lView) : null;
|
||||
|
||||
// If we should skip this injector, or if there is no injector on this node, start by searching
|
||||
// the parent injector.
|
||||
@ -330,7 +328,7 @@ export function getOrCreateInjectable<T>(
|
||||
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
|
||||
lView[injectorIndex + PARENT_INJECTOR];
|
||||
|
||||
if (!shouldSearchParent(flags, parentLocation)) {
|
||||
if (!shouldSearchParent(flags, false)) {
|
||||
injectorIndex = -1;
|
||||
} else {
|
||||
previousTView = lView[TVIEW];
|
||||
@ -350,13 +348,14 @@ export function getOrCreateInjectable<T>(
|
||||
// At this point, we have an injector which *may* contain the token, so we step through
|
||||
// the providers and directives associated with the injector's corresponding node to get
|
||||
// the instance.
|
||||
const instance: T|null =
|
||||
searchTokensOnInjector<T>(injectorIndex, lView, token, previousTView);
|
||||
const instance: T|null = searchTokensOnInjector<T>(
|
||||
injectorIndex, lView, token, previousTView, flags, hostTElementNode);
|
||||
if (instance !== NOT_FOUND) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
if (shouldSearchParent(flags, parentLocation) &&
|
||||
if (shouldSearchParent(
|
||||
flags, lView[TVIEW].data[injectorIndex + TNODE] === hostTElementNode) &&
|
||||
bloomHasToken(bloomHash, injectorIndex, lView)) {
|
||||
// The def wasn't found anywhere on this node, so it was a false positive.
|
||||
// Traverse up the tree and continue searching.
|
||||
@ -396,7 +395,7 @@ const NOT_FOUND = {};
|
||||
|
||||
function searchTokensOnInjector<T>(
|
||||
injectorIndex: number, lView: LView, token: Type<T>| InjectionToken<T>,
|
||||
previousTView: TView | null) {
|
||||
previousTView: TView | null, flags: InjectFlags, hostTElementNode: TNode | null) {
|
||||
const currentTView = lView[TVIEW];
|
||||
const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
|
||||
// First, we need to determine if view providers can be accessed by the starting element.
|
||||
@ -418,7 +417,12 @@ function searchTokensOnInjector<T>(
|
||||
// into the ViewProviders.
|
||||
(previousTView != currentTView && (tNode.type === TNodeType.Element));
|
||||
|
||||
const injectableIdx = locateDirectiveOrProvider(tNode, lView, token, canAccessViewProviders);
|
||||
// This special case happens when there is a @host on the inject and when we are searching
|
||||
// on the host element node.
|
||||
const isHostSpecialCase = (flags & InjectFlags.Host) && hostTElementNode === tNode;
|
||||
|
||||
const injectableIdx =
|
||||
locateDirectiveOrProvider(tNode, lView, token, canAccessViewProviders, isHostSpecialCase);
|
||||
if (injectableIdx !== null) {
|
||||
return getNodeInjectable(currentTView.data, lView, injectableIdx, tNode as TElementNode);
|
||||
} else {
|
||||
@ -433,13 +437,13 @@ function searchTokensOnInjector<T>(
|
||||
* @param lView The view we are currently processing
|
||||
* @param token Provider token or type of a directive to look for.
|
||||
* @param canAccessViewProviders Whether view providers should be considered.
|
||||
* @param isHostSpecialCase Whether the host special case applies.
|
||||
* @returns Index of a found directive or provider, or null when none found.
|
||||
*/
|
||||
export function locateDirectiveOrProvider<T>(
|
||||
tNode: TNode, lView: LView, token: Type<T>| InjectionToken<T>,
|
||||
canAccessViewProviders: boolean): number|null {
|
||||
tNode: TNode, lView: LView, token: Type<T>| InjectionToken<T>, canAccessViewProviders: boolean,
|
||||
isHostSpecialCase: boolean | number): number|null {
|
||||
const tView = lView[TVIEW];
|
||||
const nodeFlags = tNode.flags;
|
||||
const nodeProviderIndexes = tNode.providerIndexes;
|
||||
const tInjectables = tView.data;
|
||||
|
||||
@ -450,13 +454,21 @@ export function locateDirectiveOrProvider<T>(
|
||||
nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
|
||||
const startingIndex =
|
||||
canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount;
|
||||
for (let i = startingIndex; i < directiveEnd; i++) {
|
||||
// When the host special case applies, only the viewProviders and the component are visible
|
||||
const endIndex = isHostSpecialCase ? injectablesStart + cptViewProvidersCount : directiveEnd;
|
||||
for (let i = startingIndex; i < endIndex; i++) {
|
||||
const providerTokenOrDef = tInjectables[i] as InjectionToken<any>| Type<any>| DirectiveDef<any>;
|
||||
if (i < directivesStart && token === providerTokenOrDef ||
|
||||
i >= directivesStart && (providerTokenOrDef as DirectiveDef<any>).type === token) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (isHostSpecialCase) {
|
||||
const dirDef = tInjectables[directivesStart] as DirectiveDef<any>;
|
||||
if (dirDef && isComponentDef(dirDef) && dirDef.type === token) {
|
||||
return directivesStart;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -546,12 +558,8 @@ export function bloomHasToken(
|
||||
}
|
||||
|
||||
/** Returns true if flags prevent parent injector from being searched for tokens */
|
||||
function shouldSearchParent(flags: InjectFlags, parentLocation: RelativeInjectorLocation): boolean|
|
||||
number {
|
||||
return !(
|
||||
flags & InjectFlags.Self ||
|
||||
(flags & InjectFlags.Host &&
|
||||
((parentLocation as any as number) & RelativeInjectorLocationFlags.AcrossHostBoundary)));
|
||||
function shouldSearchParent(flags: InjectFlags, isFirstHostTNode: boolean): boolean|number {
|
||||
return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode);
|
||||
}
|
||||
|
||||
export function injectInjector() {
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
|
||||
import {assertDefined} from './assert';
|
||||
import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from './context_discovery';
|
||||
import {NodeInjector} from './di';
|
||||
@ -14,7 +15,8 @@ import {LContext} from './interfaces/context';
|
||||
import {DirectiveDef} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeProviderIndexes} from './interfaces/node';
|
||||
import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
|
||||
import {readPatchedLView, stringify} from './util';
|
||||
import {readElementValue, readPatchedLView, stringify} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -327,7 +329,7 @@ export function getListeners(element: Element): Listener[] {
|
||||
const secondParam = tCleanup[i++];
|
||||
if (typeof firstParam === 'string') {
|
||||
const name: string = firstParam;
|
||||
const listenerElement: Element = lView[secondParam];
|
||||
const listenerElement = readElementValue(lView[secondParam]) as any as Element;
|
||||
const callback: (value: any) => any = lCleanup[tCleanup[i++]];
|
||||
const useCaptureOrIndx = tCleanup[i++];
|
||||
// if useCaptureOrIndx is boolean then report it as is.
|
||||
|
24
packages/core/src/render3/empty.ts
Normal file
24
packages/core/src/render3/empty.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import './ng_dev_mode';
|
||||
|
||||
/**
|
||||
* This file contains reuseable "empty" symbols that can be used as default return values
|
||||
* in different parts of the rendering code. Because the same symbols are returned, this
|
||||
* allows for identity checks against these values to be consistently used by the framework
|
||||
* code.
|
||||
*/
|
||||
|
||||
export const EMPTY_OBJ: {} = {};
|
||||
export const EMPTY_ARRAY: any[] = [];
|
||||
|
||||
// freezing the values prevents any code from accidentally inserting new values in
|
||||
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
||||
Object.freeze(EMPTY_OBJ);
|
||||
Object.freeze(EMPTY_ARRAY);
|
||||
}
|
@ -8,8 +8,8 @@
|
||||
|
||||
import {Type} from '../../type';
|
||||
import {fillProperties} from '../../util/property';
|
||||
import {EMPTY, EMPTY_ARRAY} from '../definition';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||
import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
|
||||
|
||||
|
||||
|
||||
@ -178,7 +178,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
||||
function maybeUnwrapEmpty<T>(value: T[]): T[];
|
||||
function maybeUnwrapEmpty<T>(value: T): T;
|
||||
function maybeUnwrapEmpty(value: any): any {
|
||||
if (value === EMPTY) {
|
||||
if (value === EMPTY_OBJ) {
|
||||
return {};
|
||||
} else if (value === EMPTY_ARRAY) {
|
||||
return [];
|
||||
|
@ -93,9 +93,10 @@ function queueDestroyHooks(def: DirectiveDef<any>, tView: TView, i: number): voi
|
||||
*
|
||||
* @param currentView The current view
|
||||
*/
|
||||
export function executeInitHooks(currentView: LView, tView: TView, creationMode: boolean): void {
|
||||
if (currentView[FLAGS] & LViewFlags.RunInit) {
|
||||
executeHooks(currentView, tView.initHooks, tView.checkHooks, creationMode);
|
||||
export function executeInitHooks(
|
||||
currentView: LView, tView: TView, checkNoChangesMode: boolean): void {
|
||||
if (!checkNoChangesMode && currentView[FLAGS] & LViewFlags.RunInit) {
|
||||
executeHooks(currentView, tView.initHooks, tView.checkHooks, checkNoChangesMode);
|
||||
currentView[FLAGS] &= ~LViewFlags.RunInit;
|
||||
}
|
||||
}
|
||||
@ -106,17 +107,19 @@ export function executeInitHooks(currentView: LView, tView: TView, creationMode:
|
||||
* @param currentView The current view
|
||||
*/
|
||||
export function executeHooks(
|
||||
data: LView, allHooks: HookData | null, checkHooks: HookData | null,
|
||||
creationMode: boolean): void {
|
||||
const hooksToCall = creationMode ? allHooks : checkHooks;
|
||||
currentView: LView, allHooks: HookData | null, checkHooks: HookData | null,
|
||||
checkNoChangesMode: boolean): void {
|
||||
if (checkNoChangesMode) return;
|
||||
|
||||
const hooksToCall = currentView[FLAGS] & LViewFlags.FirstLViewPass ? allHooks : checkHooks;
|
||||
if (hooksToCall) {
|
||||
callHooks(data, hooksToCall);
|
||||
callHooks(currentView, hooksToCall);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls lifecycle hooks with their contexts, skipping init hooks if it's not
|
||||
* creation mode.
|
||||
* the first LView pass.
|
||||
*
|
||||
* @param currentView The current view
|
||||
* @param arr The array in which the hooks are found
|
||||
|
@ -48,8 +48,8 @@ export {
|
||||
|
||||
elementContainerStart,
|
||||
elementContainerEnd,
|
||||
|
||||
elementStyling,
|
||||
elementHostAttrs,
|
||||
elementStylingMap,
|
||||
elementStyleProp,
|
||||
elementStylingApply,
|
||||
@ -79,7 +79,7 @@ export {
|
||||
|
||||
directiveInject,
|
||||
injectAttribute,
|
||||
|
||||
|
||||
getCurrentView
|
||||
} from './instructions';
|
||||
|
||||
|
@ -15,6 +15,7 @@ import {Sanitizer} from '../sanitization/security';
|
||||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
import {Type} from '../type';
|
||||
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect';
|
||||
|
||||
import {assertDataInRange, assertDefined, assertEqual, assertHasParent, assertLessThan, assertNotEqual, assertPreviousIsParent} from './assert';
|
||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings';
|
||||
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
|
||||
@ -22,7 +23,7 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreat
|
||||
import {throwMultipleComponentError} from './errors';
|
||||
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
|
||||
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
|
||||
import {PlayerFactory} from './interfaces/player';
|
||||
@ -30,17 +31,17 @@ import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {SanitizerFn} from './interfaces/sanitization';
|
||||
import {StylingIndex} from './interfaces/styling';
|
||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
|
||||
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCreationMode, getCurrentDirectiveDef, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state';
|
||||
import {createStylingContextTemplate, renderStyleAndClassBindings, setStyle, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
||||
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state';
|
||||
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
||||
import {BoundPlayerFactory} from './styling/player_factory';
|
||||
import {getStylingContext, isAnimationProp} from './styling/util';
|
||||
import {createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, loadInternal, readElementValue, readPatchedLView, stringify} from './util';
|
||||
import {findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, loadInternal, readElementValue, readPatchedLView, stringify} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -60,36 +61,30 @@ const enum BindingDirection {
|
||||
* bindings, refreshes child components.
|
||||
* Note: view hooks are triggered later when leaving the view.
|
||||
*/
|
||||
export function refreshDescendantViews(lView: LView, rf: RenderFlags | null) {
|
||||
export function refreshDescendantViews(lView: LView) {
|
||||
const tView = lView[TVIEW];
|
||||
// This needs to be set before children are processed to support recursive components
|
||||
tView.firstTemplatePass = false;
|
||||
setFirstTemplatePass(false);
|
||||
|
||||
// Dynamically created views must run first only in creation mode. If this is a
|
||||
// creation-only pass, we should not call lifecycle hooks or evaluate bindings.
|
||||
// This will be done in the update-only pass.
|
||||
if (rf !== RenderFlags.Create) {
|
||||
const creationMode = getCreationMode();
|
||||
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
|
||||
// This will be done in the update pass.
|
||||
if (!isCreationMode(lView)) {
|
||||
const checkNoChangesMode = getCheckNoChangesMode();
|
||||
|
||||
if (!checkNoChangesMode) {
|
||||
executeInitHooks(lView, tView, creationMode);
|
||||
}
|
||||
executeInitHooks(lView, tView, checkNoChangesMode);
|
||||
|
||||
refreshDynamicEmbeddedViews(lView);
|
||||
|
||||
// Content query results must be refreshed before content hooks are called.
|
||||
refreshContentQueries(tView);
|
||||
|
||||
if (!checkNoChangesMode) {
|
||||
executeHooks(lView, tView.contentHooks, tView.contentCheckHooks, creationMode);
|
||||
}
|
||||
executeHooks(lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode);
|
||||
|
||||
setHostBindings(tView, lView);
|
||||
}
|
||||
|
||||
refreshChildComponents(tView.components, rf);
|
||||
refreshChildComponents(tView.components);
|
||||
}
|
||||
|
||||
|
||||
@ -147,10 +142,10 @@ function refreshContentQueries(tView: TView): void {
|
||||
}
|
||||
|
||||
/** Refreshes child components in the current view. */
|
||||
function refreshChildComponents(components: number[] | null, rf: RenderFlags | null): void {
|
||||
function refreshChildComponents(components: number[] | null): void {
|
||||
if (components != null) {
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
componentRefresh(components[i], rf);
|
||||
componentRefresh(components[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,7 +155,8 @@ export function createLView<T>(
|
||||
rendererFactory?: RendererFactory3 | null, renderer?: Renderer3 | null,
|
||||
sanitizer?: Sanitizer | null, injector?: Injector | null): LView {
|
||||
const lView = tView.blueprint.slice() as LView;
|
||||
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit;
|
||||
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit |
|
||||
LViewFlags.FirstLViewPass;
|
||||
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
|
||||
lView[CONTEXT] = context;
|
||||
lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY]) !;
|
||||
@ -303,8 +299,7 @@ export function renderTemplate<T>(
|
||||
renderer, sanitizer);
|
||||
hostView[HOST_NODE] = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
|
||||
}
|
||||
renderComponentOrTemplate(hostView, context, null, templateFn);
|
||||
|
||||
renderComponentOrTemplate(hostView, context, templateFn);
|
||||
return hostView;
|
||||
}
|
||||
|
||||
@ -348,8 +343,7 @@ export function createEmbeddedViewAndNode<T>(
|
||||
* can't store TViews in the template function itself (as we do for comps). Instead, we store the
|
||||
* TView for dynamically created views on their host TNode, which only has one instance.
|
||||
*/
|
||||
export function renderEmbeddedTemplate<T>(
|
||||
viewToRender: LView, tView: TView, context: T, rf: RenderFlags) {
|
||||
export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, context: T) {
|
||||
const _isParent = getIsParent();
|
||||
const _previousOrParentTNode = getPreviousOrParentTNode();
|
||||
setIsParent(true);
|
||||
@ -365,22 +359,17 @@ export function renderEmbeddedTemplate<T>(
|
||||
|
||||
oldView = enterView(viewToRender, viewToRender[HOST_NODE]);
|
||||
namespaceHTML();
|
||||
tView.template !(rf, context);
|
||||
if (rf & RenderFlags.Update) {
|
||||
refreshDescendantViews(viewToRender, null);
|
||||
} else {
|
||||
// This must be set to false immediately after the first creation run because in an
|
||||
// ngFor loop, all the views will be created together before update mode runs and turns
|
||||
// off firstTemplatePass. If we don't set it here, instances will perform directive
|
||||
// matching, etc again and again.
|
||||
viewToRender[TVIEW].firstTemplatePass = false;
|
||||
setFirstTemplatePass(false);
|
||||
}
|
||||
tView.template !(getRenderFlags(viewToRender), context);
|
||||
// This must be set to false immediately after the first creation run because in an
|
||||
// ngFor loop, all the views will be created together before update mode runs and turns
|
||||
// off firstTemplatePass. If we don't set it here, instances will perform directive
|
||||
// matching, etc again and again.
|
||||
viewToRender[TVIEW].firstTemplatePass = false;
|
||||
setFirstTemplatePass(false);
|
||||
|
||||
refreshDescendantViews(viewToRender);
|
||||
} finally {
|
||||
// renderEmbeddedTemplate() is called twice, once for creation only and then once for
|
||||
// update. When for creation only, leaveView() must not trigger view hooks, nor clean flags.
|
||||
const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create;
|
||||
leaveView(oldView !, isCreationOnly);
|
||||
leaveView(oldView !);
|
||||
setIsParent(_isParent);
|
||||
setPreviousOrParentTNode(_previousOrParentTNode);
|
||||
}
|
||||
@ -402,19 +391,28 @@ export function nextContext<T = any>(level: number = 1): T {
|
||||
}
|
||||
|
||||
function renderComponentOrTemplate<T>(
|
||||
hostView: LView, componentOrContext: T, rf: RenderFlags | null,
|
||||
templateFn?: ComponentTemplate<T>) {
|
||||
hostView: LView, context: T, templateFn?: ComponentTemplate<T>) {
|
||||
const rendererFactory = hostView[RENDERER_FACTORY];
|
||||
const oldView = enterView(hostView, hostView[HOST_NODE]);
|
||||
try {
|
||||
if (rendererFactory.begin) {
|
||||
rendererFactory.begin();
|
||||
}
|
||||
if (templateFn) {
|
||||
namespaceHTML();
|
||||
templateFn(rf || getRenderFlags(hostView), componentOrContext !);
|
||||
|
||||
if (isCreationMode(hostView)) {
|
||||
// creation mode pass
|
||||
if (templateFn) {
|
||||
namespaceHTML();
|
||||
templateFn(RenderFlags.Create, context !);
|
||||
}
|
||||
|
||||
refreshDescendantViews(hostView);
|
||||
hostView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
}
|
||||
refreshDescendantViews(hostView, rf);
|
||||
|
||||
// update mode pass
|
||||
templateFn && templateFn(RenderFlags.Update, context !);
|
||||
refreshDescendantViews(hostView);
|
||||
} finally {
|
||||
if (rendererFactory.end) {
|
||||
rendererFactory.end();
|
||||
@ -425,16 +423,11 @@ function renderComponentOrTemplate<T>(
|
||||
|
||||
/**
|
||||
* This function returns the default configuration of rendering flags depending on when the
|
||||
* template is in creation mode or update mode. By default, the update block is run with the
|
||||
* creation block when the view is in creation mode. Otherwise, the update block is run
|
||||
* alone.
|
||||
*
|
||||
* Dynamically created views do NOT use this configuration (update block and create block are
|
||||
* always run separately).
|
||||
* template is in creation mode or update mode. Update block and create block are
|
||||
* always run separately.
|
||||
*/
|
||||
function getRenderFlags(view: LView): RenderFlags {
|
||||
return view[FLAGS] & LViewFlags.CreationMode ? RenderFlags.Create | RenderFlags.Update :
|
||||
RenderFlags.Update;
|
||||
return isCreationMode(view) ? RenderFlags.Create : RenderFlags.Update;
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
@ -464,7 +457,8 @@ export function namespaceHTML() {
|
||||
*
|
||||
* @param index Index of the element in the data array
|
||||
* @param name Name of the DOM Node
|
||||
* @param attrs Statically bound set of attributes to be written into the DOM element on creation.
|
||||
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
|
||||
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
*/
|
||||
export function element(
|
||||
@ -534,7 +528,8 @@ export function elementContainerEnd(): void {
|
||||
*
|
||||
* @param index Index of the element in the LView array
|
||||
* @param name Name of the DOM Node
|
||||
* @param attrs Statically bound set of attributes to be written into the DOM element on creation.
|
||||
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
|
||||
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
*
|
||||
* Attributes and localRefs are passed as an array of strings where elements with an even index
|
||||
@ -558,6 +553,14 @@ export function elementStart(
|
||||
const tNode = createNodeAtIndex(index, TNodeType.Element, native !, name, attrs || null);
|
||||
|
||||
if (attrs) {
|
||||
// it's important to only prepare styling-related datastructures once for a given
|
||||
// tNode and not each time an element is created. Also, the styling code is designed
|
||||
// to be patched and constructed at various points, but only up until the first element
|
||||
// is created. Then the styling context is locked and can only be instantiated for each
|
||||
// successive element that is created.
|
||||
if (tView.firstTemplatePass && !tNode.stylingTemplate && hasStyling(attrs)) {
|
||||
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
|
||||
}
|
||||
setUpAttributes(native, attrs);
|
||||
}
|
||||
|
||||
@ -571,6 +574,23 @@ export function elementStart(
|
||||
attachPatchData(native, lView);
|
||||
}
|
||||
increaseElementDepthCount();
|
||||
|
||||
// if a directive contains a host binding for "class" then all class-based data will
|
||||
// flow through that (except for `[class.prop]` bindings). This also includes initial
|
||||
// static class values as well. (Note that this will be fixed once map-based `[style]`
|
||||
// and `[class]` bindings work for multiple directives.)
|
||||
if (tView.firstTemplatePass) {
|
||||
const inputData = initializeTNodeInputs(tNode);
|
||||
if (inputData && inputData.hasOwnProperty('class')) {
|
||||
tNode.flags |= TNodeFlags.hasClassInput;
|
||||
}
|
||||
}
|
||||
|
||||
// There is no point in rendering styles when a class directive is present since
|
||||
// it will take that over for us (this will be removed once #FW-882 is in).
|
||||
if (tNode.stylingTemplate && (tNode.flags & TNodeFlags.hasClassInput) === 0) {
|
||||
renderInitialStylesAndClasses(native, tNode.stylingTemplate, lView[RENDERER]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -729,25 +749,28 @@ function setUpAttributes(native: RElement, attrs: TAttributes): void {
|
||||
let i = 0;
|
||||
|
||||
while (i < attrs.length) {
|
||||
const attrName = attrs[i];
|
||||
if (attrName === AttributeMarker.SelectOnly) break;
|
||||
if (attrName === NG_PROJECT_AS_ATTR_NAME) {
|
||||
i += 2;
|
||||
} else {
|
||||
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||
const attrName = attrs[i++];
|
||||
if (typeof attrName == 'number') {
|
||||
if (attrName === AttributeMarker.NamespaceURI) {
|
||||
// Namespaced attributes
|
||||
const namespaceURI = attrs[i + 1] as string;
|
||||
const attrName = attrs[i + 2] as string;
|
||||
const attrVal = attrs[i + 3] as string;
|
||||
const namespaceURI = attrs[i++] as string;
|
||||
const attrName = attrs[i++] as string;
|
||||
const attrVal = attrs[i++] as string;
|
||||
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||
isProc ?
|
||||
(renderer as ProceduralRenderer3)
|
||||
.setAttribute(native, attrName, attrVal, namespaceURI) :
|
||||
native.setAttributeNS(namespaceURI, attrName, attrVal);
|
||||
i += 4;
|
||||
} else {
|
||||
// All other `AttributeMarker`s are ignored here.
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/// attrName is string;
|
||||
const attrVal = attrs[i++];
|
||||
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
|
||||
// Standard attributes
|
||||
const attrVal = attrs[i + 1];
|
||||
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||
if (isAnimationProp(attrName)) {
|
||||
if (isProc) {
|
||||
(renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal);
|
||||
@ -758,7 +781,6 @@ function setUpAttributes(native: RElement, attrs: TAttributes): void {
|
||||
.setAttribute(native, attrName as string, attrVal as string) :
|
||||
native.setAttribute(attrName as string, attrVal as string);
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -910,6 +932,15 @@ export function elementEnd(): void {
|
||||
|
||||
queueLifecycleHooks(getLView()[TVIEW], previousOrParentTNode);
|
||||
decreaseElementDepthCount();
|
||||
|
||||
// this is fired at the end of elementEnd because ALL of the stylingBindings code
|
||||
// (for directives and the template) have now executed which means the styling
|
||||
// context can be instantiated properly.
|
||||
if (hasClassInput(previousOrParentTNode)) {
|
||||
const stylingContext = getStylingContext(previousOrParentTNode.index, lView);
|
||||
setInputsForProperty(
|
||||
lView, previousOrParentTNode.inputs !['class'] !, getInitialClassNameValue(stylingContext));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1104,119 +1135,84 @@ function generatePropertyAliases(tNode: TNode, direction: BindingDirection): Pro
|
||||
return propStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or remove a class in a `classList` on a DOM element.
|
||||
*
|
||||
* This instruction is meant to handle the [class.foo]="exp" case
|
||||
*
|
||||
* @param index The index of the element to update in the data array
|
||||
* @param classIndex Index of class to toggle. Because it is going to DOM, this is not subject to
|
||||
* renaming as part of minification.
|
||||
* @param value A value indicating if a given class should be added or removed.
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
*/
|
||||
export function elementClassProp(
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory, directive?: {}): void {
|
||||
if (directive != undefined) {
|
||||
return hackImplementationOfElementClassProp(
|
||||
index, classIndex, value, directive); // proper supported in next PR
|
||||
}
|
||||
const val =
|
||||
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
|
||||
updateElementClassProp(getStylingContext(index + HEADER_OFFSET, getLView()), classIndex, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign any inline style values to the element during creation mode.
|
||||
*
|
||||
* This instruction is meant to be called during creation mode to apply all styling
|
||||
* (e.g. `style="..."`) values to the element. This is also where the provided index
|
||||
* value is allocated for the styling details for its corresponding element (the element
|
||||
* index is the previous index value from this one).
|
||||
* This instruction is meant to be called during creation mode to register all
|
||||
* dynamic style and class bindings on the element. Note for static values (no binding)
|
||||
* see `elementStart` and `elementHostAttrs`.
|
||||
*
|
||||
* (Note this function calls `elementStylingApply` immediately when called.)
|
||||
* @param classBindingNames An array containing bindable class names.
|
||||
* The `elementClassProp` refers to the class name by index in this array.
|
||||
* (i.e. `['foo', 'bar']` means `foo=0` and `bar=1`).
|
||||
* @param styleBindingNames An array containing bindable style properties.
|
||||
* The `elementStyleProp` refers to the class name by index in this array.
|
||||
* (i.e. `['width', 'height']` means `width=0` and `height=1`).
|
||||
* @param styleSanitizer An optional sanitizer function that will be used to sanitize any CSS
|
||||
* property values that are applied to the element (during rendering).
|
||||
* Note that the sanitizer instance itself is tied to the `directive` (if provided).
|
||||
* @param directive A directive instance the styling is associated with. If not provided
|
||||
* current view's controller instance is assumed.
|
||||
*
|
||||
*
|
||||
* @param index Index value which will be allocated to store styling data for the element.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param classDeclarations A key/value array of CSS classes that will be registered on the element.
|
||||
* Each individual style will be used on the element as long as it is not overridden
|
||||
* by any classes placed on the element by multiple (`[class]`) or singular (`[class.named]`)
|
||||
* bindings. If a class binding changes its value to a falsy value then the matching initial
|
||||
* class value that are passed in here will be applied to the element (if matched).
|
||||
* @param styleDeclarations A key/value array of CSS styles that will be registered on the element.
|
||||
* Each individual style will be used on the element as long as it is not overridden
|
||||
* by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`)
|
||||
* bindings. If a style binding changes its value to null then the initial styling
|
||||
* values that are passed in here will be applied to the element (if matched).
|
||||
* @param styleSanitizer An optional sanitizer function that will be used (if provided)
|
||||
* to sanitize the any CSS property values that are applied to the element (during rendering).
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStyling(
|
||||
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
classBindingNames?: string[] | null, styleBindingNames?: string[] | null,
|
||||
styleSanitizer?: StyleSanitizeFn | null, directive?: {}): void {
|
||||
if (directive != undefined) {
|
||||
getCreationMode() &&
|
||||
hackImplementationOfElementStyling(
|
||||
classDeclarations || null, styleDeclarations || null, styleSanitizer || null,
|
||||
directive); // supported in next PR
|
||||
return;
|
||||
}
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
const inputData = initializeTNodeInputs(tNode);
|
||||
|
||||
if (!tNode.stylingTemplate) {
|
||||
const hasClassInput = inputData && inputData.hasOwnProperty('class') ? true : false;
|
||||
if (hasClassInput) {
|
||||
tNode.flags |= TNodeFlags.hasClassInput;
|
||||
}
|
||||
|
||||
// initialize the styling template.
|
||||
tNode.stylingTemplate = createStylingContextTemplate(
|
||||
classDeclarations, styleDeclarations, styleSanitizer, hasClassInput);
|
||||
}
|
||||
|
||||
if (styleDeclarations && styleDeclarations.length ||
|
||||
classDeclarations && classDeclarations.length) {
|
||||
const index = tNode.index;
|
||||
if (delegateToClassInput(tNode)) {
|
||||
const lView = getLView();
|
||||
const stylingContext = getStylingContext(index, lView);
|
||||
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
|
||||
setInputsForProperty(lView, tNode.inputs !['class'] !, initialClasses);
|
||||
}
|
||||
elementStylingApply(index - HEADER_OFFSET);
|
||||
tNode.stylingTemplate = createEmptyStylingContext();
|
||||
}
|
||||
updateContextWithBindings(
|
||||
tNode.stylingTemplate !, directive || null, classBindingNames, styleBindingNames,
|
||||
styleSanitizer, hasClassInput(tNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign static styling values to a host element.
|
||||
*
|
||||
* NOTE: This instruction is meant to used from `hostBindings` function only.
|
||||
*
|
||||
* @param directive A directive instance the styling is associated with.
|
||||
* @param attrs An array containing class and styling information. The values must be marked with
|
||||
* `AttributeMarker`.
|
||||
*
|
||||
* ```
|
||||
* var attrs = [AttributeMarker.Classes, 'foo', 'bar',
|
||||
* AttributeMarker.Styles, 'width', '100px', 'height, '200px']
|
||||
* elementHostAttrs(directive, attrs);
|
||||
* ```
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementHostAttrs(directive: any, attrs: TAttributes) {
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
if (!tNode.stylingTemplate) {
|
||||
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
|
||||
}
|
||||
patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all styling values to the element which have been queued by any styling instructions.
|
||||
* Apply styling binding to the element.
|
||||
*
|
||||
* This instruction is meant to be run once one or more `elementStyle` and/or `elementStyleProp`
|
||||
* have been issued against the element. This function will also determine if any styles have
|
||||
* changed and will then skip the operation if there is nothing new to render.
|
||||
* This instruction is meant to be run after `elementStyle` and/or `elementStyleProp`.
|
||||
* if any styling bindings have changed then the changes are flushed to the element.
|
||||
*
|
||||
* Once called then all queued styles will be flushed.
|
||||
*
|
||||
* @param index Index of the element's styling storage that will be rendered.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
* @param index Index of the element's with which styling is associated.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
components
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStylingApply(index: number, directive?: {}): void {
|
||||
if (directive != undefined) {
|
||||
return hackImplementationOfElementStylingApply(index, directive); // supported in next PR
|
||||
}
|
||||
export function elementStylingApply(index: number, directive?: any): void {
|
||||
const lView = getLView();
|
||||
const isFirstRender = (lView[FLAGS] & LViewFlags.CreationMode) !== 0;
|
||||
const totalPlayersQueued = renderStyleAndClassBindings(
|
||||
getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender);
|
||||
const isFirstRender = (lView[FLAGS] & LViewFlags.FirstLViewPass) !== 0;
|
||||
const totalPlayersQueued = renderStyling(
|
||||
getStylingContext(index + HEADER_OFFSET, lView), lView[RENDERER], lView, isFirstRender, null,
|
||||
null, directive);
|
||||
if (totalPlayersQueued > 0) {
|
||||
const rootContext = getRootContext(lView);
|
||||
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
|
||||
@ -1224,29 +1220,34 @@ export function elementStylingApply(index: number, directive?: {}): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a given style to be rendered on an Element.
|
||||
* Update a style bindings value on an element.
|
||||
*
|
||||
* If the style value is `null` then it will be removed from the element
|
||||
* (or assigned a different value depending if there are any styles placed
|
||||
* on the element with `elementStyle` or any styles that are present
|
||||
* from when the element was created (with `elementStyling`).
|
||||
*
|
||||
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
|
||||
* (Note that the styling element is updated as part of `elementStylingApply`.)
|
||||
*
|
||||
* @param index Index of the element's styling storage to change in the data array.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param styleIndex Index of the style property on this element. (Monotonically increasing.)
|
||||
* @param value New value to write (null to remove).
|
||||
* @param index Index of the element's with which styling is associated.
|
||||
* @param styleIndex Index of style to update. This index value refers to the
|
||||
* index of the style in the style bindings array that was passed into
|
||||
* `elementStlyingBindings`.
|
||||
* @param value New value to write (null to remove). Note that if a directive also
|
||||
* attempts to write to the same binding value then it will only be able to
|
||||
* do so if the template binding value is `null` (or doesn't exist at all).
|
||||
* @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
|
||||
* Note that when a suffix is provided then the underlying sanitizer will
|
||||
* be ignored.
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
components
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStyleProp(
|
||||
index: number, styleIndex: number, value: string | number | String | PlayerFactory | null,
|
||||
suffix?: string, directive?: {}): void {
|
||||
suffix?: string | null, directive?: {}): void {
|
||||
let valueToAdd: string|null = null;
|
||||
if (value !== null) {
|
||||
if (suffix) {
|
||||
@ -1261,35 +1262,59 @@ export function elementStyleProp(
|
||||
valueToAdd = value as any as string;
|
||||
}
|
||||
}
|
||||
if (directive != undefined) {
|
||||
hackImplementationOfElementStyleProp(index, styleIndex, valueToAdd, suffix, directive);
|
||||
} else {
|
||||
updateElementStyleProp(
|
||||
getStylingContext(index + HEADER_OFFSET, getLView()), styleIndex, valueToAdd);
|
||||
}
|
||||
updateElementStyleProp(
|
||||
getStylingContext(index + HEADER_OFFSET, getLView()), styleIndex, valueToAdd, directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a key/value map of styles to be rendered on an Element.
|
||||
* Add or remove a class via a class binding on a DOM element.
|
||||
*
|
||||
* This instruction is meant to handle the `[style]="exp"` usage. When styles are applied to
|
||||
* the Element they will then be placed with respect to any styles set with `elementStyleProp`.
|
||||
* If any styles are set to `null` then they will be removed from the element (unless the same
|
||||
* style properties have been assigned to the element during creation using `elementStyling`).
|
||||
* This instruction is meant to handle the [class.foo]="exp" case and, therefore,
|
||||
* the class itself must already be applied using `elementStyling` within
|
||||
* the creation block.
|
||||
*
|
||||
* @param index Index of the element's with which styling is associated.
|
||||
* @param classIndex Index of class to toggle. This index value refers to the
|
||||
* index of the class in the class bindings array that was passed into
|
||||
* `elementStlyingBindings` (which is meant to be called before this
|
||||
* function is).
|
||||
* @param value A true/false value which will turn the class on or off.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
components
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementClassProp(
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory, directive?: {}): void {
|
||||
const onOrOffClassValue =
|
||||
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
|
||||
updateElementClassProp(
|
||||
getStylingContext(index + HEADER_OFFSET, getLView()), classIndex, onOrOffClassValue,
|
||||
directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update style and/or class bindings using object literal.
|
||||
*
|
||||
* This instruction is meant apply styling via the `[style]="exp"` and `[class]="exp"` template
|
||||
* bindings. When styles are applied to the Element they will then be placed with respect to
|
||||
* any styles set with `elementStyleProp`. If any styles are set to `null` then they will be
|
||||
* removed from the element.
|
||||
*
|
||||
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
|
||||
*
|
||||
* @param index Index of the element's styling storage to change in the data array.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param index Index of the element's with which styling is associated.
|
||||
* @param classes A key/value style map of CSS classes that will be added to the given element.
|
||||
* Any missing classes (that have already been applied to the element beforehand) will be
|
||||
* removed (unset) from the element's list of CSS classes.
|
||||
* @param styles A key/value style map of the styles that will be applied to the given element.
|
||||
* Any missing styles (that have already been applied to the element beforehand) will be
|
||||
* removed (unset) from the element's styling.
|
||||
* @param directive the ref to the directive that is attempting to change styling.
|
||||
* @param directive Directive instance that is attempting to change styling. (Defaults to the
|
||||
* component of the current view).
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function elementStylingMap<T>(
|
||||
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
||||
@ -1300,113 +1325,24 @@ export function elementStylingMap<T>(
|
||||
const lView = getLView();
|
||||
const tNode = getTNode(index, lView);
|
||||
const stylingContext = getStylingContext(index + HEADER_OFFSET, lView);
|
||||
if (delegateToClassInput(tNode) && classes !== NO_CHANGE) {
|
||||
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
|
||||
if (hasClassInput(tNode) && classes !== NO_CHANGE) {
|
||||
const initialClasses = getInitialClassNameValue(stylingContext);
|
||||
const classInputVal =
|
||||
(initialClasses.length ? (initialClasses + ' ') : '') + (classes as string);
|
||||
setInputsForProperty(lView, tNode.inputs !['class'] !, classInputVal);
|
||||
} else {
|
||||
updateStylingMap(stylingContext, classes, styles);
|
||||
}
|
||||
updateStylingMap(stylingContext, classes, styles);
|
||||
}
|
||||
|
||||
/* START OF HACK BLOCK */
|
||||
/*
|
||||
* HACK
|
||||
* The code below is a quick and dirty implementation of the host style binding so that we can make
|
||||
* progress on TestBed. Once the correct implementation is created this code should be removed.
|
||||
*/
|
||||
interface HostStylingHack {
|
||||
classDeclarations: string[];
|
||||
styleDeclarations: string[];
|
||||
styleSanitizer: StyleSanitizeFn|null;
|
||||
}
|
||||
type HostStylingHackMap = Map<{}, HostStylingHack>;
|
||||
|
||||
function hackImplementationOfElementStyling(
|
||||
classDeclarations: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleDeclarations: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleSanitizer: StyleSanitizeFn | null, directive: {}): void {
|
||||
const node = getNativeByTNode(getPreviousOrParentTNode(), getLView()) as RElement;
|
||||
ngDevMode && assertDefined(node, 'expecting parent DOM node');
|
||||
const hostStylingHackMap: HostStylingHackMap =
|
||||
((node as any).hostStylingHack || ((node as any).hostStylingHack = new Map()));
|
||||
const squashedClassDeclarations = hackSquashDeclaration(classDeclarations);
|
||||
hostStylingHackMap.set(directive, {
|
||||
classDeclarations: squashedClassDeclarations,
|
||||
styleDeclarations: hackSquashDeclaration(styleDeclarations), styleSanitizer
|
||||
});
|
||||
hackSetStaticClasses(node, squashedClassDeclarations);
|
||||
}
|
||||
|
||||
function hackSetStaticClasses(node: RElement, classDeclarations: (string | boolean)[]) {
|
||||
// Static classes need to be set here because static classes don't generate
|
||||
// elementClassProp instructions.
|
||||
const lView = getLView();
|
||||
const staticClassStartIndex =
|
||||
classDeclarations.indexOf(InitialStylingFlags.VALUES_MODE as any) + 1;
|
||||
const renderer = lView[RENDERER];
|
||||
|
||||
for (let i = staticClassStartIndex; i < classDeclarations.length; i += 2) {
|
||||
const className = classDeclarations[i] as string;
|
||||
const value = classDeclarations[i + 1];
|
||||
// if value is true, then this is a static class and we should set it now.
|
||||
// class bindings are set separately in elementClassProp.
|
||||
if (value === true) {
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
renderer.addClass(node, className);
|
||||
} else {
|
||||
const classList = (node as HTMLElement).classList;
|
||||
classList.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hackSquashDeclaration(declarations: (string | boolean | InitialStylingFlags)[] | null):
|
||||
string[] {
|
||||
// assume the array is correct. This should be fine for View Engine compatibility.
|
||||
return declarations || [] as any;
|
||||
}
|
||||
|
||||
function hackImplementationOfElementClassProp(
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory, directive: {}): void {
|
||||
const lView = getLView();
|
||||
const node = getNativeByIndex(index, lView);
|
||||
ngDevMode && assertDefined(node, 'could not locate node');
|
||||
const hostStylingHack: HostStylingHack = (node as any).hostStylingHack.get(directive);
|
||||
const className = hostStylingHack.classDeclarations[classIndex];
|
||||
const renderer = lView[RENDERER];
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
value ? renderer.addClass(node, className) : renderer.removeClass(node, className);
|
||||
} else {
|
||||
const classList = (node as HTMLElement).classList;
|
||||
value ? classList.add(className) : classList.remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStylingApply(index: number, directive?: {}): void {
|
||||
// Do nothing because the hack implementation is eager.
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStyleProp(
|
||||
index: number, styleIndex: number, value: string | null, suffix?: string,
|
||||
directive?: {}): void {
|
||||
const lView = getLView();
|
||||
const node = getNativeByIndex(index, lView);
|
||||
ngDevMode && assertDefined(node, 'could not locate node');
|
||||
const hostStylingHack: HostStylingHack = (node as any).hostStylingHack.get(directive);
|
||||
const styleName = hostStylingHack.styleDeclarations[styleIndex];
|
||||
const renderer = lView[RENDERER];
|
||||
setStyle(node, styleName, value as string, renderer, null);
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStylingMap<T>(
|
||||
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
||||
styles?: {[styleName: string]: any} | NO_CHANGE | null, directive?: {}): void {
|
||||
throw new Error('unimplemented. Should not be needed by ViewEngine compatibility');
|
||||
}
|
||||
|
||||
/* END OF HACK BLOCK */
|
||||
|
||||
//////////////////////////
|
||||
//// Text
|
||||
//////////////////////////
|
||||
@ -1992,11 +1928,9 @@ export function containerRefreshStart(index: number): void {
|
||||
|
||||
lView[index + HEADER_OFFSET][ACTIVE_INDEX] = 0;
|
||||
|
||||
if (!getCheckNoChangesMode()) {
|
||||
// We need to execute init hooks here so ngOnInit hooks are called in top level views
|
||||
// before they are called in embedded views (for backwards compatibility).
|
||||
executeInitHooks(lView, tView, getCreationMode());
|
||||
}
|
||||
// We need to execute init hooks here so ngOnInit hooks are called in top level views
|
||||
// before they are called in embedded views (for backwards compatibility).
|
||||
executeInitHooks(lView, tView, getCheckNoChangesMode());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2041,9 +1975,7 @@ function refreshDynamicEmbeddedViews(lView: LView) {
|
||||
const dynamicViewData = container[VIEWS][i];
|
||||
// The directives and pipes are not needed here as an existing view is only being refreshed.
|
||||
ngDevMode && assertDefined(dynamicViewData[TVIEW], 'TView must be allocated');
|
||||
renderEmbeddedTemplate(
|
||||
dynamicViewData, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !,
|
||||
RenderFlags.Update);
|
||||
renderEmbeddedTemplate(dynamicViewData, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2118,13 +2050,14 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num
|
||||
enterView(viewToRender, viewToRender[TVIEW].node);
|
||||
}
|
||||
if (lContainer) {
|
||||
if (getCreationMode()) {
|
||||
if (isCreationMode(viewToRender)) {
|
||||
// it is a new view, insert it into collection of views for a given container
|
||||
insertView(viewToRender, lContainer, lView, lContainer[ACTIVE_INDEX] !, -1);
|
||||
}
|
||||
lContainer[ACTIVE_INDEX] !++;
|
||||
}
|
||||
return getRenderFlags(viewToRender);
|
||||
return isCreationMode(viewToRender) ? RenderFlags.Create | RenderFlags.Update :
|
||||
RenderFlags.Update;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2158,7 +2091,12 @@ function getOrCreateEmbeddedTView(
|
||||
export function embeddedViewEnd(): void {
|
||||
const lView = getLView();
|
||||
const viewHost = lView[HOST_NODE];
|
||||
refreshDescendantViews(lView, null);
|
||||
|
||||
if (isCreationMode(lView)) {
|
||||
refreshDescendantViews(lView); // creation mode pass
|
||||
lView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
}
|
||||
refreshDescendantViews(lView); // update mode pass
|
||||
leaveView(lView[PARENT] !);
|
||||
setPreviousOrParentTNode(viewHost !);
|
||||
setIsParent(false);
|
||||
@ -2170,9 +2108,8 @@ export function embeddedViewEnd(): void {
|
||||
* Refreshes components by entering the component view and processing its bindings, queries, etc.
|
||||
*
|
||||
* @param adjustedElementIndex Element index in LView[] (adjusted for HEADER_OFFSET)
|
||||
* @param rf The render flags that should be used to process this template
|
||||
*/
|
||||
export function componentRefresh<T>(adjustedElementIndex: number, rf: RenderFlags | null): void {
|
||||
export function componentRefresh<T>(adjustedElementIndex: number): void {
|
||||
const lView = getLView();
|
||||
ngDevMode && assertDataInRange(lView, adjustedElementIndex);
|
||||
const hostView = getComponentViewByIndex(adjustedElementIndex, lView);
|
||||
@ -2181,7 +2118,7 @@ export function componentRefresh<T>(adjustedElementIndex: number, rf: RenderFlag
|
||||
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
|
||||
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
||||
syncViewWithBlueprint(hostView);
|
||||
detectChangesInternal(hostView, hostView[CONTEXT], rf);
|
||||
checkView(hostView, hostView[CONTEXT]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2461,7 +2398,7 @@ export function tick<T>(component: T): void {
|
||||
function tickRootContext(rootContext: RootContext) {
|
||||
for (let i = 0; i < rootContext.components.length; i++) {
|
||||
const rootComponent = rootContext.components[i];
|
||||
renderComponentOrTemplate(readPatchedLView(rootComponent) !, rootComponent, RenderFlags.Update);
|
||||
renderComponentOrTemplate(readPatchedLView(rootComponent) !, rootComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2479,7 +2416,21 @@ function tickRootContext(rootContext: RootContext) {
|
||||
* @param component The component which the change detection should be performed on.
|
||||
*/
|
||||
export function detectChanges<T>(component: T): void {
|
||||
detectChangesInternal(getComponentViewByInstance(component) !, component, null);
|
||||
const view = getComponentViewByInstance(component) !;
|
||||
detectChangesInternal<T>(view, component);
|
||||
}
|
||||
|
||||
export function detectChangesInternal<T>(view: LView, context: T) {
|
||||
const rendererFactory = view[RENDERER_FACTORY];
|
||||
|
||||
if (rendererFactory.begin) rendererFactory.begin();
|
||||
|
||||
if (isCreationMode(view)) {
|
||||
checkView(view, context); // creation mode pass
|
||||
}
|
||||
checkView(view, context); // update mode pass
|
||||
|
||||
if (rendererFactory.end) rendererFactory.end();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2526,7 +2477,7 @@ export function checkNoChangesInRootView(lView: LView): void {
|
||||
}
|
||||
|
||||
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
|
||||
export function detectChangesInternal<T>(hostView: LView, component: T, rf: RenderFlags | null) {
|
||||
export function checkView<T>(hostView: LView, component: T) {
|
||||
const hostTView = hostView[TVIEW];
|
||||
const oldView = enterView(hostView, hostView[HOST_NODE]);
|
||||
const templateFn = hostTView.template !;
|
||||
@ -2534,27 +2485,23 @@ export function detectChangesInternal<T>(hostView: LView, component: T, rf: Rend
|
||||
|
||||
try {
|
||||
namespaceHTML();
|
||||
createViewQuery(viewQuery, rf, hostView[FLAGS], component);
|
||||
templateFn(rf || getRenderFlags(hostView), component);
|
||||
refreshDescendantViews(hostView, rf);
|
||||
updateViewQuery(viewQuery, hostView[FLAGS], component);
|
||||
createViewQuery(viewQuery, hostView, component);
|
||||
templateFn(getRenderFlags(hostView), component);
|
||||
refreshDescendantViews(hostView);
|
||||
updateViewQuery(viewQuery, hostView, component);
|
||||
} finally {
|
||||
leaveView(oldView, rf === RenderFlags.Create);
|
||||
leaveView(oldView);
|
||||
}
|
||||
}
|
||||
|
||||
function createViewQuery<T>(
|
||||
viewQuery: ComponentQuery<{}>| null, renderFlags: RenderFlags | null, viewFlags: LViewFlags,
|
||||
component: T): void {
|
||||
if (viewQuery && (renderFlags === RenderFlags.Create ||
|
||||
(renderFlags === null && (viewFlags & LViewFlags.CreationMode)))) {
|
||||
function createViewQuery<T>(viewQuery: ComponentQuery<{}>| null, view: LView, component: T): void {
|
||||
if (viewQuery && isCreationMode(view)) {
|
||||
viewQuery(RenderFlags.Create, component);
|
||||
}
|
||||
}
|
||||
|
||||
function updateViewQuery<T>(
|
||||
viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void {
|
||||
if (viewQuery && flags & RenderFlags.Update) {
|
||||
function updateViewQuery<T>(viewQuery: ComponentQuery<{}>| null, view: LView, component: T): void {
|
||||
if (viewQuery && !isCreationMode(view)) {
|
||||
viewQuery(RenderFlags.Update, component);
|
||||
}
|
||||
}
|
||||
@ -2882,10 +2829,6 @@ function initializeTNodeInputs(tNode: TNode | null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function delegateToClassInput(tNode: TNode) {
|
||||
return tNode.flags & TNodeFlags.hasClassInput;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current OpaqueViewState instance.
|
||||
|
@ -23,6 +23,14 @@ export const VIEWS = 1;
|
||||
// As we already have these constants in LView, we don't need to re-create them.
|
||||
export const NATIVE = 6;
|
||||
export const RENDER_PARENT = 7;
|
||||
// Because interfaces in TS/JS cannot be instanceof-checked this means that we
|
||||
// need to rely on predictable characteristics of data-structures to check if they
|
||||
// are what we expect for them to be. The `LContainer` interface code below has a
|
||||
// fixed length and the constant value below references that. Using the length value
|
||||
// below we can predictably gaurantee that we are dealing with an `LContainer` array.
|
||||
// This value MUST be kept up to date with the length of the `LContainer` array
|
||||
// interface below so that runtime type checking can work.
|
||||
export const LCONTAINER_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* The state associated with a container.
|
||||
|
@ -344,8 +344,4 @@ export type PipeTypeList =
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
||||
export const enum InitialStylingFlags {
|
||||
VALUES_MODE = 0b1,
|
||||
}
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
@ -26,7 +26,6 @@ export interface RelativeInjectorLocation { __brand__: 'RelativeInjectorLocation
|
||||
|
||||
export const enum RelativeInjectorLocationFlags {
|
||||
InjectorIndexMask = 0b111111111111111,
|
||||
AcrossHostBoundary = 0b1000000000000000,
|
||||
ViewOffsetShift = 16,
|
||||
NO_PARENT = -1,
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export const enum TNodeProviderIndexes {
|
||||
CptViewProvidersCountShifter = 0b00000000000000010000000000000000,
|
||||
}
|
||||
/**
|
||||
* A set of marker values to be used in the attributes arrays. Those markers indicate that some
|
||||
* A set of marker values to be used in the attributes arrays. These markers indicate that some
|
||||
* items are not regular attributes and the processing should be adapted accordingly.
|
||||
*/
|
||||
export const enum AttributeMarker {
|
||||
@ -65,13 +65,50 @@ export const enum AttributeMarker {
|
||||
*/
|
||||
NamespaceURI = 0,
|
||||
|
||||
/**
|
||||
* Signals class declaration.
|
||||
*
|
||||
* Each value following `Classes` designates a class name to include on the element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div class="foo bar baz">...<d/vi>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Classes, 'foo', 'bar', 'baz'];
|
||||
* ```
|
||||
*/
|
||||
Classes = 1,
|
||||
|
||||
/**
|
||||
* Signals style declaration.
|
||||
*
|
||||
* Each pair of values following `Styles` designates a style name and value to include on the
|
||||
* element.
|
||||
* ## Example:
|
||||
*
|
||||
* Given:
|
||||
* ```
|
||||
* <div style="width:100px; height:200px; color:red">...</div>
|
||||
* ```
|
||||
*
|
||||
* the generated code is:
|
||||
* ```
|
||||
* var _c1 = [AttributeMarker.Styles, 'width', '100px', 'height'. '200px', 'color', 'red'];
|
||||
* ```
|
||||
*/
|
||||
Styles = 2,
|
||||
|
||||
/**
|
||||
* This marker indicates that the following attribute names were extracted from bindings (ex.:
|
||||
* [foo]="exp") and / or event handlers (ex. (bar)="doSth()").
|
||||
* Taking the above bindings and outputs as an example an attributes array could look as follows:
|
||||
* ['class', 'fade in', AttributeMarker.SelectOnly, 'foo', 'bar']
|
||||
*/
|
||||
SelectOnly = 1
|
||||
SelectOnly = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,130 +9,199 @@ import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {PlayerContext} from './player';
|
||||
|
||||
|
||||
/**
|
||||
* The styling context acts as a styling manifest (shaped as an array) for determining which
|
||||
* styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp`
|
||||
* and `updateClassProp` functions. There are also two initialization functions
|
||||
* `allocStylingContext` and `createStylingContextTemplate` which are used to initialize
|
||||
* and/or clone the context.
|
||||
* and `updateClassProp` functions. It also stores the static style/class values that were
|
||||
* extracted from the template by the compiler.
|
||||
*
|
||||
* The context is an array where the first two cells are used for static data (initial styling)
|
||||
* and dirty flags / index offsets). The remaining set of cells is used for multi (map) and single
|
||||
* (prop) style values.
|
||||
* A context is created by Angular when:
|
||||
* 1. An element contains static styling values (like style="..." or class="...")
|
||||
* 2. An element contains single property binding values (like [style.prop]="x" or
|
||||
* [class.prop]="y")
|
||||
* 3. An element contains multi property binding values (like [style]="x" or [class]="y")
|
||||
* 4. A directive contains host bindings for static, single or multi styling properties/bindings.
|
||||
* 5. An animation player is added to an element via `addPlayer`
|
||||
*
|
||||
* each value from here onwards is mapped as so:
|
||||
* [i] = mutation/type flag for the style/class value
|
||||
* [i + 1] = prop string (or null incase it has been removed)
|
||||
* [i + 2] = value string (or null incase it has been removed)
|
||||
*
|
||||
* There are three types of styling types stored in this context:
|
||||
* initial: any styles that are passed in once the context is created
|
||||
* (these are stored in the first cell of the array and the first
|
||||
* value of this array is always `null` even if no initial styling exists.
|
||||
* the `null` value is there so that any new styles have a parent to point
|
||||
* to. This way we can always assume that there is a parent.)
|
||||
*
|
||||
* single: any styles that are updated using `updateStyleProp` or `updateClassProp` (fixed set)
|
||||
*
|
||||
* multi: any styles that are updated using `updateStylingMap` (dynamic set)
|
||||
*
|
||||
* Note that context is only used to collect style information. Only when `renderStyling`
|
||||
* is called is when the styling payload will be rendered (or built as a key/value map).
|
||||
*
|
||||
* When the context is created, depending on what initial styling values are passed in, the
|
||||
* context itself will be pre-filled with slots based on the initial style properties. Say
|
||||
* for example we have a series of initial styles that look like so:
|
||||
*
|
||||
* style="width:100px; height:200px;"
|
||||
* class="foo"
|
||||
*
|
||||
* Then the initial state of the context (once initialized) will look like so:
|
||||
* Note that even if an element contains static styling then this context will be created and
|
||||
* attached to it. The reason why this happens (instead of treating styles/classes as regular
|
||||
* HTML attributes) is because the style/class bindings must be able to default themselves back
|
||||
* to their respective static values when they are set to null.
|
||||
*
|
||||
* Say for example we have this:
|
||||
* ```
|
||||
* <!-- when myWidthExp=null then a width of "100px"
|
||||
* will be used a default value for width -->
|
||||
* <div style="width:100px" [style.width]="myWidthExp"></div>
|
||||
* ```
|
||||
*
|
||||
* Even in the situation where there are no bindings, the static styling is still placed into the
|
||||
* context because there may be another directive on the same element that has styling.
|
||||
*
|
||||
* When Angular initializes styling data for an element then it will first register the static
|
||||
* styling values on the element using one of these two instructions:
|
||||
*
|
||||
* 1. elementStart or element (within the template function of a component)
|
||||
* 2. elementHostAttrs (for directive host bindings)
|
||||
*
|
||||
* In either case, a styling context will be created and stored within an element's LViewData. Once
|
||||
* the styling context is created then single and multi properties can stored within it. For this to
|
||||
* happen, the following function needs to be called:
|
||||
*
|
||||
* `elementStyling` (called with style properties, class properties and a sanitizer + a directive
|
||||
* instance).
|
||||
*
|
||||
* When this instruction is called it will populate the styling context with the provided style
|
||||
* and class names into the context.
|
||||
*
|
||||
* The context itself looks like this:
|
||||
*
|
||||
* context = [
|
||||
* element,
|
||||
* playerContext | null,
|
||||
* styleSanitizer | null,
|
||||
* [null, '100px', '200px', true], // property names are not needed since they have already been
|
||||
* written to DOM.
|
||||
*
|
||||
* configMasterVal,
|
||||
* 1, // this instructs how many `style` values there are so that class index values can be
|
||||
* offsetted
|
||||
* { classOne: true, classTwo: false } | 'classOne classTwo' | null // last class value provided
|
||||
* into updateStylingMap
|
||||
* { styleOne: '100px', styleTwo: 0 } | null // last style value provided into updateStylingMap
|
||||
*
|
||||
* // 8
|
||||
* 'width',
|
||||
* pointers(1, 15); // Point to static `width`: `100px` and multi `width`.
|
||||
* null,
|
||||
*
|
||||
* // 11
|
||||
* 'height',
|
||||
* pointers(2, 18); // Point to static `height`: `200px` and multi `height`.
|
||||
* null,
|
||||
*
|
||||
* // 14
|
||||
* 'foo',
|
||||
* pointers(1, 21); // Point to static `foo`: `true` and multi `foo`.
|
||||
* null,
|
||||
*
|
||||
* // 17
|
||||
* 'width',
|
||||
* pointers(1, 6); // Point to static `width`: `100px` and single `width`.
|
||||
* null,
|
||||
*
|
||||
* // 21
|
||||
* 'height',
|
||||
* pointers(2, 9); // Point to static `height`: `200px` and single `height`.
|
||||
* null,
|
||||
*
|
||||
* // 24
|
||||
* 'foo',
|
||||
* pointers(3, 12); // Point to static `foo`: `true` and single `foo`.
|
||||
* null,
|
||||
* // 0-8: header values (about 8 entries of configuration data)
|
||||
* // 9+: this is where each entry is stored:
|
||||
* ]
|
||||
*
|
||||
* function pointers(staticIndex: number, dynamicIndex: number) {
|
||||
* // combine the two indices into a single word.
|
||||
* return (staticIndex << StylingFlags.BitCountSize) |
|
||||
* (dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize));
|
||||
* Let's say we have the following template code:
|
||||
*
|
||||
* ```
|
||||
* <div class="foo bar"
|
||||
* style="width:200px; color:red"
|
||||
* [style.width]="myWidthExp"
|
||||
* [style.height]="myHeightExp"
|
||||
* [class.baz]="myBazExp">
|
||||
* ```
|
||||
*
|
||||
* The context generated from these values will look like this (note that
|
||||
* for each binding name (the class and style bindings) the values will
|
||||
* be inserted twice into the array (once for single property entries) and
|
||||
* another for multi property entries).
|
||||
*
|
||||
* context = [
|
||||
* // 0-8: header values (about 8 entries of configuration data)
|
||||
* // 9+: this is where each entry is stored:
|
||||
*
|
||||
* // SINGLE PROPERTIES
|
||||
* configForWidth,
|
||||
* 'width'
|
||||
* myWidthExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForHeight,
|
||||
* 'height'
|
||||
* myHeightExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForBazClass,
|
||||
* 'baz
|
||||
* myBazClassExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* // MULTI PROPERTIES
|
||||
* configForWidth,
|
||||
* 'width'
|
||||
* myWidthExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForHeight,
|
||||
* 'height'
|
||||
* myHeightExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
*
|
||||
* configForBazClass,
|
||||
* 'baz
|
||||
* myBazClassExp, // the binding value not the binding itself
|
||||
* 0, // the directive owner
|
||||
* ]
|
||||
*
|
||||
* The configuration values are left out of the example above because
|
||||
* the ordering of them could change between code patches. Please read the
|
||||
* documentation below to get a better understand of what the configuration
|
||||
* values are and how they work.
|
||||
*
|
||||
* Each time a binding property is updated (whether it be through a single
|
||||
* property instruction like `elementStyleProp`, `elementClassProp` or
|
||||
* `elementStylingMap`) then the values in the context will be updated as
|
||||
* well.
|
||||
*
|
||||
* If for example `[style.width]` updates to `555px` then its value will be reflected
|
||||
* in the context as so:
|
||||
*
|
||||
* context = [
|
||||
* // ...
|
||||
* configForWidth, // this will be marked DIRTY
|
||||
* 'width'
|
||||
* '555px',
|
||||
* 0,
|
||||
* //..
|
||||
* ]
|
||||
*
|
||||
* The context and directive data will also be marked dirty.
|
||||
*
|
||||
* Despite the context being updated, nothing has been rendered on screen (not styles or
|
||||
* classes have been set on the element). To kick off rendering for an element the following
|
||||
* function needs to be run `elementStylingApply`.
|
||||
*
|
||||
* `elementStylingApply` will run through the context and find each dirty value and render them onto
|
||||
* the element. Once complete, all styles/classes will be set to clean. Because of this, the render
|
||||
* function will now know not to rerun itself again if called again unless new style/class values
|
||||
* have changed.
|
||||
*
|
||||
* ## Directives
|
||||
* Directives style values (which are provided through host bindings) are also supported and
|
||||
* housed within the same styling context as are template-level style/class properties/bindings.
|
||||
* Both directive-level and template-level styling bindings share the same context.
|
||||
*
|
||||
* Each of the following instructions supports accepting a directive instance as an input parameter:
|
||||
*
|
||||
* - `elementHostAttrs`
|
||||
* - `elementStyling`
|
||||
* - `elementStyleProp`
|
||||
* - `elementClassProp`
|
||||
* - `elementStylingMap`
|
||||
* - `elementStylingApply`
|
||||
*
|
||||
* Each time a directiveRef is passed in, it will be converted into an index by examining the
|
||||
* directive registry (which lives in the context configuration area). The index is then used
|
||||
* to help single style properties figure out where a value is located in the context.
|
||||
*
|
||||
* If two directives or a directive + a template binding both write to the same style/class
|
||||
* binding then the styling context code will decide which one wins based on the following
|
||||
* rule:
|
||||
*
|
||||
* 1. If the template binding has a value then it always wins
|
||||
* 2. If not then whichever first-registered directive that has that value first will win
|
||||
*
|
||||
* The code example helps make this clear:
|
||||
*
|
||||
* ```
|
||||
* <div [style.width]="myWidth" [my-width-directive]="'600px">
|
||||
* @Directive({ selector: '[my-width-directive' ]})
|
||||
* class MyWidthDirective {
|
||||
* @Input('my-width-directive')
|
||||
* @HostBinding('style.width')
|
||||
* public width = null;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The values are duplicated so that space is set aside for both multi ([style] and [class])
|
||||
* and single ([style.prop] or [class.named]) values. The respective config values
|
||||
* (configValA, configValB, etc...) are a combination of the StylingFlags with two index
|
||||
* values: the `initialIndex` (which points to the index location of the style value in
|
||||
* the initial styles array in slot 0) and the `dynamicIndex` (which points to the
|
||||
* matching single/multi index position in the context array for the same prop).
|
||||
* Since there is a style binding for width present on the element (`[style.width]`) then
|
||||
* it will always win over the width binding that is present as a host binding within
|
||||
* the `MyWidthDirective`. However, if `[style.width]` renders as `null` (so `myWidth=null`)
|
||||
* then the `MyWidthDirective` will be able to write to the `width` style within the context.
|
||||
* Simply put, whichever directive writes to a value ends up having ownership of it.
|
||||
*
|
||||
* This means that every time `updateStyleProp` or `updateClassProp` are called then they
|
||||
* must be called using an index value (not a property string) which references the index
|
||||
* value of the initial style prop/class when the context was created. This also means that
|
||||
* `updateStyleProp` or `updateClassProp` cannot be called with a new property (only
|
||||
* `updateStylingMap` can include new CSS properties that will be added to the context).
|
||||
* The way in which the ownership is facilitated is through index value. The earliest directives
|
||||
* get the smallest index values (with 0 being reserved for the template element bindings). Each
|
||||
* time a value is written from a directive or the template bindings, the value itself gets
|
||||
* assigned the directive index value in its data. If another directive writes a value again then
|
||||
* its directive index gets compared against the directive index that exists on the element. Only
|
||||
* when the new value's directive index is less than the existing directive index then the new
|
||||
* value will be written to the context.
|
||||
*
|
||||
* Each directive also has its own sanitizer and dirty flags. These values are consumed within the
|
||||
* rendering function.
|
||||
*/
|
||||
export interface StylingContext extends Array<InitialStyles|{[key: string]: any}|number|string|
|
||||
boolean|RElement|StyleSanitizeFn|PlayerContext|null> {
|
||||
/**
|
||||
* Location of animation context (which contains the active players) for this element styling
|
||||
* context.
|
||||
*/
|
||||
[StylingIndex.PlayerContext]: PlayerContext|null;
|
||||
|
||||
/**
|
||||
* The style sanitizer that is used within this context
|
||||
*/
|
||||
[StylingIndex.StyleSanitizerPosition]: StyleSanitizeFn|null;
|
||||
|
||||
/**
|
||||
* Location of initial data shared by all instances of this style.
|
||||
*/
|
||||
[StylingIndex.InitialStylesPosition]: InitialStyles;
|
||||
|
||||
export interface StylingContext extends
|
||||
Array<{[key: string]: any}|number|string|boolean|RElement|StyleSanitizeFn|PlayerContext|null> {
|
||||
/**
|
||||
* A numeric value representing the configuration status (whether the context is dirty or not)
|
||||
* mixed together (using bit shifting) with a index value which tells the starting index value
|
||||
@ -140,12 +209,27 @@ export interface StylingContext extends Array<InitialStyles|{[key: string]: any}
|
||||
*/
|
||||
[StylingIndex.MasterFlagPosition]: number;
|
||||
|
||||
/**
|
||||
* Location of the collection of directives for this context
|
||||
*/
|
||||
[StylingIndex.DirectiveRegistryPosition]: DirectiveRegistryValues;
|
||||
|
||||
/**
|
||||
* Location of all static styles values
|
||||
*/
|
||||
[StylingIndex.InitialStyleValuesPosition]: InitialStylingValues;
|
||||
|
||||
/**
|
||||
* Location of all static class values
|
||||
*/
|
||||
[StylingIndex.InitialClassValuesPosition]: InitialStylingValues;
|
||||
|
||||
/**
|
||||
* A numeric value representing the class index offset value. Whenever a single class is
|
||||
* applied (using `elementClassProp`) it should have an styling index value that doesn't
|
||||
* need to take into account any style values that exist in the context.
|
||||
*/
|
||||
[StylingIndex.ClassOffsetPosition]: number;
|
||||
[StylingIndex.SinglePropOffsetPositions]: SinglePropOffsetValues;
|
||||
|
||||
/**
|
||||
* Location of element that is used as a target for this context.
|
||||
@ -156,24 +240,206 @@ export interface StylingContext extends Array<InitialStyles|{[key: string]: any}
|
||||
* The last class value that was interpreted by elementStylingMap. This is cached
|
||||
* So that the algorithm can exit early incase the value has not changed.
|
||||
*/
|
||||
[StylingIndex.PreviousOrCachedMultiClassValue]: {[key: string]: any}|string|null;
|
||||
[StylingIndex.CachedClassValueOrInitialClassString]: {[key: string]: any}|string|(string)[]|null;
|
||||
|
||||
/**
|
||||
* The last style value that was interpreted by elementStylingMap. This is cached
|
||||
* So that the algorithm can exit early incase the value has not changed.
|
||||
*/
|
||||
[StylingIndex.PreviousMultiStyleValue]: {[key: string]: any}|null;
|
||||
[StylingIndex.CachedStyleValue]: {[key: string]: any}|(string)[]|null;
|
||||
|
||||
/**
|
||||
* Location of animation context (which contains the active players) for this element styling
|
||||
* context.
|
||||
*/
|
||||
[StylingIndex.PlayerContext]: PlayerContext|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The initial styles is populated whether or not there are any initial styles passed into
|
||||
* the context during allocation. The 0th value must be null so that index values of `0` within
|
||||
* the context flags can always point to a null value safely when nothing is set.
|
||||
* Used as a styling array to house static class and style values that were extracted
|
||||
* by the compiler and placed in the animation context via `elementStart` and
|
||||
* `elementHostAttrs`.
|
||||
*
|
||||
* All other entries in this array are of `string` value and correspond to the values that
|
||||
* were extracted from the `style=""` attribute in the HTML code for the provided template.
|
||||
* See [InitialStylingValuesIndex] for a breakdown of how all this works.
|
||||
*/
|
||||
export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
|
||||
export interface InitialStylingValues extends Array<string|boolean|null> { [0]: null; }
|
||||
|
||||
/**
|
||||
* Used as an offset/position index to figure out where initial styling
|
||||
* values are located.
|
||||
*
|
||||
* Used as a reference point to provide markers to all static styling
|
||||
* values (the initial style and class values on an element) within an
|
||||
* array within the StylingContext. This array contains key/value pairs
|
||||
* where the key is the style property name or className and the value is
|
||||
* the style value or whether or not a class is present on the elment.
|
||||
*
|
||||
* The first value is also always null so that a initial index value of
|
||||
* `0` will always point to a null value.
|
||||
*
|
||||
* If a <div> elements contains a list of static styling values like so:
|
||||
*
|
||||
* <div class="foo bar baz" style="width:100px; height:200px;">
|
||||
*
|
||||
* Then the initial styles for that will look like so:
|
||||
*
|
||||
* Styles:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'width', '100px', height, '200px'
|
||||
* ]
|
||||
*
|
||||
* Classes:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'foo', true, 'bar', true, 'baz', true
|
||||
* ]
|
||||
*
|
||||
* Initial style and class entries have their own arrays. This is because
|
||||
* it's easier to add to the end of one array and not then have to update
|
||||
* every context entries' pointer index to the newly offseted values.
|
||||
*
|
||||
* When property bindinds are added to a context then initial style/class
|
||||
* values will also be inserted into the array. This is to create a space
|
||||
* in the situation when a follow-up directive inserts static styling into
|
||||
* the array. By default style values are `null` and class values are
|
||||
* `false` when inserted by property bindings.
|
||||
*
|
||||
* For example:
|
||||
* <div class="foo bar baz"
|
||||
* [class.car]="myCarExp"
|
||||
* style="width:100px; height:200px;"
|
||||
* [style.opacity]="myOpacityExp">
|
||||
*
|
||||
* Will construct initial styling values that look like:
|
||||
*
|
||||
* Styles:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'width', '100px', height, '200px', 'opacity', null
|
||||
* ]
|
||||
*
|
||||
* Classes:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'foo', true, 'bar', true, 'baz', true, 'car', false
|
||||
* ]
|
||||
*
|
||||
* Now if a directive comes along and introduces `car` as a static
|
||||
* class value or `opacity` then those values will be filled into
|
||||
* the initial styles array.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* @Directive({
|
||||
* selector: 'opacity-car-directive',
|
||||
* host: {
|
||||
* 'style': 'opacity:0.5',
|
||||
* 'class': 'car'
|
||||
* }
|
||||
* })
|
||||
* class OpacityCarDirective {}
|
||||
*
|
||||
* This will render itself as:
|
||||
*
|
||||
* Styles:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'width', '100px', height, '200px', 'opacity', null
|
||||
* ]
|
||||
*
|
||||
* Classes:
|
||||
* StylingContext[InitialStylesIndex] = [
|
||||
* null, 'foo', true, 'bar', true, 'baz', true, 'car', false
|
||||
* ]
|
||||
*/
|
||||
export const enum InitialStylingValuesIndex {
|
||||
KeyValueStartPosition = 1,
|
||||
PropOffset = 0,
|
||||
ValueOffset = 1,
|
||||
Size = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* An array located in the StylingContext that houses all directive instances and additional
|
||||
* data about them.
|
||||
*
|
||||
* Each entry in this array represents a source of where style/class binding values could
|
||||
* come from. By default, there is always at least one directive here with a null value and
|
||||
* that represents bindings that live directly on an element (not host bindings).
|
||||
*
|
||||
* Each successive entry in the array is an actual instance of an array as well as some
|
||||
* additional info.
|
||||
*
|
||||
* An entry within this array has the following values:
|
||||
* [0] = The instance of the directive (or null when it is not a directive, but a template binding
|
||||
* source)
|
||||
* [1] = The pointer that tells where the single styling (stuff like [class.foo] and [style.prop])
|
||||
* offset values are located. This value will allow for a binding instruction to find exactly
|
||||
* where a style is located.
|
||||
* [2] = Whether or not the directive has any styling values that are dirty. This is used as
|
||||
* reference within the renderClassAndStyleBindings function to decide whether to skip
|
||||
* iterating through the context when rendering is executed.
|
||||
* [3] = The styleSanitizer instance that is assigned to the directive. Although it's unlikely,
|
||||
* a directive could introduce its own special style sanitizer and for this reach each
|
||||
* directive will get its own space for it (if null then the very first sanitizer is used).
|
||||
*
|
||||
* Each time a new directive is added it will insert these four values at the end of the array.
|
||||
* When this array is examined (using indexOf) then the resulting directiveIndex will be resolved
|
||||
* by dividing the index value by the size of the array entries (so if DirA is at spot 8 then its
|
||||
* index will be 2).
|
||||
*/
|
||||
export interface DirectiveRegistryValues extends Array<null|{}|boolean|number|StyleSanitizeFn> {
|
||||
[DirectiveRegistryValuesIndex.DirectiveValueOffset]: null;
|
||||
[DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset]: number;
|
||||
[DirectiveRegistryValuesIndex.DirtyFlagOffset]: boolean;
|
||||
[DirectiveRegistryValuesIndex.StyleSanitizerOffset]: StyleSanitizeFn|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum that outlines the offset/position values for each directive entry and its data
|
||||
* that are housed inside of [DirectiveRegistryValues].
|
||||
*/
|
||||
export const enum DirectiveRegistryValuesIndex {
|
||||
DirectiveValueOffset = 0,
|
||||
SinglePropValuesIndexOffset = 1,
|
||||
DirtyFlagOffset = 2,
|
||||
StyleSanitizerOffset = 3,
|
||||
Size = 4
|
||||
}
|
||||
|
||||
/**
|
||||
* An array that contains the index pointer values for every single styling property
|
||||
* that exists in the context and for every directive. It also contains the total
|
||||
* single styles and single classes that exists in the context as the first two values.
|
||||
*
|
||||
* Let's say we have the following template code:
|
||||
*
|
||||
* <div [style.width]="myWidth"
|
||||
* [style.height]="myHeight"
|
||||
* [class.flipped]="flipClass"
|
||||
* directive-with-opacity>
|
||||
* directive-with-foo-bar-classes>
|
||||
*
|
||||
* We have two directive and template-binding sources,
|
||||
* 2 + 1 styles and 1 + 1 classes. When the bindings are
|
||||
* registered the SinglePropOffsets array will look like so:
|
||||
*
|
||||
* s_0/c_0 = template directive value
|
||||
* s_1/c_1 = directive one (directive-with-opacity)
|
||||
* s_2/c_2 = directive two (directive-with-foo-bar-classes)
|
||||
*
|
||||
* [3, 2, 2, 1, s_00, s01, c_01, 1, 0, s_10, 0, 1, c_20
|
||||
*/
|
||||
export interface SinglePropOffsetValues extends Array<number> {
|
||||
[SinglePropOffsetValuesIndex.StylesCountPosition]: number;
|
||||
[SinglePropOffsetValuesIndex.ClassesCountPosition]: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum that outlines the offset/position values for each single prop/class entry
|
||||
* that are housed inside of [SinglePropOffsetValues].
|
||||
*/
|
||||
export const enum SinglePropOffsetValuesIndex {
|
||||
StylesCountPosition = 0,
|
||||
ClassesCountPosition = 1,
|
||||
ValueStartPosition = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to set the context to be dirty or not both on the master flag (position 1)
|
||||
@ -181,47 +447,49 @@ export interface InitialStyles extends Array<string|null|boolean> { [0]: null; }
|
||||
*/
|
||||
export const enum StylingFlags {
|
||||
// Implies no configurations
|
||||
None = 0b00000,
|
||||
None = 0b000000,
|
||||
// Whether or not the entry or context itself is dirty
|
||||
Dirty = 0b00001,
|
||||
Dirty = 0b000001,
|
||||
// Whether or not this is a class-based assignment
|
||||
Class = 0b00010,
|
||||
Class = 0b000010,
|
||||
// Whether or not a sanitizer was applied to this property
|
||||
Sanitize = 0b00100,
|
||||
Sanitize = 0b000100,
|
||||
// Whether or not any player builders within need to produce new players
|
||||
PlayerBuildersDirty = 0b01000,
|
||||
PlayerBuildersDirty = 0b001000,
|
||||
// If NgClass is present (or some other class handler) then it will handle the map expressions and
|
||||
// initial classes
|
||||
OnlyProcessSingleClasses = 0b10000,
|
||||
OnlyProcessSingleClasses = 0b010000,
|
||||
// The max amount of bits used to represent these configuration values
|
||||
BitCountSize = 5,
|
||||
// There are only five bits here
|
||||
BitMask = 0b11111
|
||||
BindingAllocationLocked = 0b100000,
|
||||
BitCountSize = 6,
|
||||
// There are only six bits here
|
||||
BitMask = 0b111111
|
||||
}
|
||||
|
||||
/** Used as numeric pointer values to determine what cells to update in the `StylingContext` */
|
||||
export const enum StylingIndex {
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
PlayerContext = 0,
|
||||
// Position of where the style sanitizer is stored within the styling context
|
||||
StyleSanitizerPosition = 1,
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
InitialStylesPosition = 2,
|
||||
// Index of location where the start of single properties are stored. (`updateStyleProp`)
|
||||
MasterFlagPosition = 3,
|
||||
MasterFlagPosition = 0,
|
||||
// Position of where the registered directives exist for this styling context
|
||||
DirectiveRegistryPosition = 1,
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
InitialStyleValuesPosition = 2,
|
||||
InitialClassValuesPosition = 3,
|
||||
// Index of location where the class index offset value is located
|
||||
ClassOffsetPosition = 4,
|
||||
SinglePropOffsetPositions = 4,
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
// This index must align with HOST, see interfaces/view.ts
|
||||
ElementPosition = 5,
|
||||
// Position of where the last string-based CSS class value was stored (or a cached version of the
|
||||
// initial styles when a [class] directive is present)
|
||||
PreviousOrCachedMultiClassValue = 6,
|
||||
CachedClassValueOrInitialClassString = 6,
|
||||
// Position of where the last string-based CSS class value was stored
|
||||
PreviousMultiStyleValue = 7,
|
||||
// Location of single (prop) value entries are stored within the context
|
||||
SingleStylesStartPosition = 8,
|
||||
CachedStyleValue = 7,
|
||||
// Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue
|
||||
// Position of where the initial styles are stored in the styling context
|
||||
PlayerContext = 8,
|
||||
// Location of single (prop) value entries are stored within the context
|
||||
SingleStylesStartPosition = 9,
|
||||
FlagsOffset = 0,
|
||||
PropertyOffset = 1,
|
||||
ValueOffset = 2,
|
||||
@ -233,3 +501,16 @@ export const enum StylingIndex {
|
||||
// The binary digit value as a mask
|
||||
BitMask = 0b11111111111111, // 14 bits
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum that outlines the bit flag data for directive owner and player index
|
||||
* values that exist within en entry that lives in the StylingContext.
|
||||
*
|
||||
* The values here split a number value into two sets of bits:
|
||||
* - The first 16 bits are used to store the directiveIndex that owns this style value
|
||||
* - The other 16 bits are used to store the playerBuilderIndex that is attached to this style
|
||||
*/
|
||||
export const enum DirectiveOwnerAndPlayerBuilderIndex {
|
||||
BitCountSize = 16,
|
||||
BitMask = 0b1111111111111111
|
||||
}
|
||||
|
@ -221,16 +221,25 @@ export const enum LViewFlags {
|
||||
* back into the parent view, `data` will be defined and `creationMode` will be
|
||||
* improperly reported as false.
|
||||
*/
|
||||
CreationMode = 0b0000001,
|
||||
CreationMode = 0b000000001,
|
||||
|
||||
/**
|
||||
* Whether or not this LView instance is on its first processing pass.
|
||||
*
|
||||
* An LView instance is considered to be on its "first pass" until it
|
||||
* has completed one creation mode run and one update mode run. At this
|
||||
* time, the flag is turned off.
|
||||
*/
|
||||
FirstLViewPass = 0b000000010,
|
||||
|
||||
/** Whether this view has default change detection strategy (checks always) or onPush */
|
||||
CheckAlways = 0b0000010,
|
||||
CheckAlways = 0b000000100,
|
||||
|
||||
/** Whether or not this view is currently dirty (needing check) */
|
||||
Dirty = 0b0000100,
|
||||
Dirty = 0b000001000,
|
||||
|
||||
/** Whether or not this view is currently attached to change detection tree. */
|
||||
Attached = 0b0001000,
|
||||
Attached = 0b000010000,
|
||||
|
||||
/**
|
||||
* Whether or not the init hooks have run.
|
||||
@ -239,13 +248,13 @@ export const enum LViewFlags {
|
||||
* runs OR the first cR() instruction that runs (so inits are run for the top level view before
|
||||
* any embedded views).
|
||||
*/
|
||||
RunInit = 0b0010000,
|
||||
RunInit = 0b000100000,
|
||||
|
||||
/** Whether or not this view is destroyed. */
|
||||
Destroyed = 0b0100000,
|
||||
Destroyed = 0b001000000,
|
||||
|
||||
/** Whether or not this view is the root view */
|
||||
IsRoot = 0b1000000,
|
||||
IsRoot = 0b010000000,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,10 +134,13 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
|
||||
encapsulation: ViewEncapsulation;
|
||||
viewProviders: Provider[]|null;
|
||||
interpolation?: [string, string];
|
||||
changeDetection?: ChangeDetectionStrategy;
|
||||
}
|
||||
|
||||
export type ViewEncapsulation = number;
|
||||
|
||||
export type ChangeDetectionStrategy = number;
|
||||
|
||||
export interface R3QueryMetadataFacade {
|
||||
propertyName: string;
|
||||
first: boolean;
|
||||
|
@ -12,9 +12,9 @@ import {Component, Directive} from '../../metadata/directives';
|
||||
import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading';
|
||||
import {ViewEncapsulation} from '../../metadata/view';
|
||||
import {Type} from '../../type';
|
||||
import {stringify} from '../../util';
|
||||
import {EMPTY_ARRAY} from '../definition';
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF} from '../fields';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {R3DirectiveMetadataFacade, getCompilerFacade} from './compiler_facade';
|
||||
import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from './compiler_facade_interface';
|
||||
@ -61,6 +61,7 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
||||
animations: metadata.animations,
|
||||
viewQueries: extractQueriesMetadata(type, getReflect().propMetadata(type), isViewQuery),
|
||||
directives: [],
|
||||
changeDetection: metadata.changeDetection,
|
||||
pipes: new Map(),
|
||||
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated,
|
||||
interpolation: metadata.interpolation,
|
||||
@ -153,8 +154,6 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
||||
};
|
||||
}
|
||||
|
||||
const EMPTY_OBJ = {};
|
||||
|
||||
function convertToR3QueryPredicate(selector: any): any|string[] {
|
||||
return typeof selector === 'string' ? splitByComma(selector) : selector;
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||
'ɵregisterContentQuery': r3.registerContentQuery,
|
||||
'ɵreference': r3.reference,
|
||||
'ɵelementStyling': r3.elementStyling,
|
||||
'ɵelementHostAttrs': r3.elementHostAttrs,
|
||||
'ɵelementStylingMap': r3.elementStylingMap,
|
||||
'ɵelementStyleProp': r3.elementStyleProp,
|
||||
'ɵelementStylingApply': r3.elementStylingApply,
|
||||
|
@ -15,7 +15,7 @@ import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'
|
||||
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
||||
import {CLEANUP, CONTAINER_INDEX, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {assertNodeType} from './node_assert';
|
||||
import {getNativeByTNode, isLContainer, isRootView, readElementValue, stringify} from './util';
|
||||
import {findComponentView, getNativeByTNode, isLContainer, isRootView, readElementValue, stringify} from './util';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
||||
|
||||
@ -195,24 +195,6 @@ function walkTNodeTree(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a current view, finds the nearest component's host (LElement).
|
||||
*
|
||||
* @param lView LView for which we want a host element node
|
||||
* @returns The host node
|
||||
*/
|
||||
export function findComponentView(lView: LView): LView {
|
||||
let rootTNode = lView[HOST_NODE];
|
||||
|
||||
while (rootTNode && rootTNode.type === TNodeType.View) {
|
||||
ngDevMode && assertDefined(lView[PARENT], 'lView.parent');
|
||||
lView = lView[PARENT] !;
|
||||
rootTNode = lView[HOST_NODE];
|
||||
}
|
||||
|
||||
return lView;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
|
||||
* being passed as an argument.
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
|
||||
import {getBindingRoot, getCreationMode, getLView} from './state';
|
||||
import {getBindingRoot, getLView, isCreationMode} from './state';
|
||||
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ export function pureFunction0<T>(slotOffset: number, pureFn: () => T, thisArg?:
|
||||
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
|
||||
const bindingIndex = getBindingRoot() + slotOffset;
|
||||
const lView = getLView();
|
||||
return getCreationMode() ?
|
||||
return isCreationMode() ?
|
||||
updateBinding(lView, bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) :
|
||||
getBinding(lView, bindingIndex);
|
||||
}
|
||||
|
@ -250,7 +250,8 @@ function queryByReadToken(read: any, tNode: TNode, currentView: LView): any {
|
||||
if (typeof factoryFn === 'function') {
|
||||
return factoryFn();
|
||||
} else {
|
||||
const matchingIdx = locateDirectiveOrProvider(tNode, currentView, read as Type<any>, false);
|
||||
const matchingIdx =
|
||||
locateDirectiveOrProvider(tNode, currentView, read as Type<any>, false, false);
|
||||
if (matchingIdx !== null) {
|
||||
return getNodeInjectable(
|
||||
currentView[TVIEW].data, currentView, matchingIdx, tNode as TElementNode);
|
||||
@ -304,7 +305,7 @@ function add(
|
||||
if (type === ViewEngine_TemplateRef) {
|
||||
result = queryByTemplateRef(type, tNode, currentView, predicate.read);
|
||||
} else {
|
||||
const matchingIdx = locateDirectiveOrProvider(tNode, currentView, type, false);
|
||||
const matchingIdx = locateDirectiveOrProvider(tNode, currentView, type, false, false);
|
||||
if (matchingIdx !== null) {
|
||||
result = queryRead(tNode, currentView, predicate.read, matchingIdx);
|
||||
}
|
||||
|
@ -186,14 +186,9 @@ export function getOrCreateCurrentQueries(
|
||||
return currentQueries || (lView[QUERIES] = new QueryType(null, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* This property gets set before entering a template.
|
||||
*/
|
||||
let creationMode: boolean;
|
||||
|
||||
export function getCreationMode(): boolean {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return creationMode;
|
||||
/** Checks whether a given view is in creation mode */
|
||||
export function isCreationMode(view: LView = lView): boolean {
|
||||
return (view[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -276,8 +271,6 @@ export function enterView(newView: LView, hostTNode: TElementNode | TViewNode |
|
||||
const oldView = lView;
|
||||
if (newView) {
|
||||
const tView = newView[TVIEW];
|
||||
|
||||
creationMode = (newView[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
||||
firstTemplatePass = tView.firstTemplatePass;
|
||||
bindingRootIndex = tView.bindingStartIndex;
|
||||
}
|
||||
@ -320,19 +313,17 @@ export function resetComponentState() {
|
||||
* the direction of traversal (up or down the view tree) a bit clearer.
|
||||
*
|
||||
* @param newView New state to become active
|
||||
* @param creationOnly An optional boolean to indicate that the view was processed in creation mode
|
||||
* only, i.e. the first update will be done later. Only possible for dynamically created views.
|
||||
*/
|
||||
export function leaveView(newView: LView, creationOnly?: boolean): void {
|
||||
export function leaveView(newView: LView): void {
|
||||
const tView = lView[TVIEW];
|
||||
if (!creationOnly) {
|
||||
if (!checkNoChangesMode) {
|
||||
executeHooks(lView, tView.viewHooks, tView.viewCheckHooks, creationMode);
|
||||
}
|
||||
if (isCreationMode(lView)) {
|
||||
lView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
} else {
|
||||
executeHooks(lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode);
|
||||
// Views are clean and in update mode after being checked, so these bits are cleared
|
||||
lView[FLAGS] &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
|
||||
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
|
||||
lView[FLAGS] |= LViewFlags.RunInit;
|
||||
lView[BINDING_INDEX] = tView.bindingStartIndex;
|
||||
}
|
||||
lView[FLAGS] |= LViewFlags.RunInit;
|
||||
lView[BINDING_INDEX] = tView.bindingStartIndex;
|
||||
enterView(newView, null);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,12 +9,13 @@ import '../ng_dev_mode';
|
||||
|
||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
import {getLContext} from '../context_discovery';
|
||||
import {ACTIVE_INDEX, LContainer} from '../interfaces/container';
|
||||
import {LContainer} from '../interfaces/container';
|
||||
import {LContext} from '../interfaces/context';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node';
|
||||
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {InitialStyles, StylingContext, StylingIndex} from '../interfaces/styling';
|
||||
import {FLAGS, HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
|
||||
import {InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
|
||||
import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
|
||||
import {getTNode} from '../util';
|
||||
|
||||
import {CorePlayerHandler} from './core_player_handler';
|
||||
@ -23,16 +24,18 @@ const ANIMATION_PROP_PREFIX = '@';
|
||||
|
||||
export function createEmptyStylingContext(
|
||||
element?: RElement | null, sanitizer?: StyleSanitizeFn | null,
|
||||
initialStylingValues?: InitialStyles): StylingContext {
|
||||
initialStyles?: InitialStylingValues | null,
|
||||
initialClasses?: InitialStylingValues | null): StylingContext {
|
||||
return [
|
||||
null, // PlayerContext
|
||||
sanitizer || null, // StyleSanitizer
|
||||
initialStylingValues || [null], // InitialStyles
|
||||
0, // MasterFlags
|
||||
0, // ClassOffset
|
||||
element || null, // Element
|
||||
null, // PreviousMultiClassValue
|
||||
null // PreviousMultiStyleValue
|
||||
0, // MasterFlags
|
||||
[null, -1, false, sanitizer || null], // DirectiveRefs
|
||||
initialStyles || [null], // InitialStyles
|
||||
initialClasses || [null], // InitialClasses
|
||||
[0, 0], // SinglePropOffsets
|
||||
element || null, // Element
|
||||
null, // PreviousMultiClassValue
|
||||
null, // PreviousMultiStyleValue
|
||||
null, // PlayerContext
|
||||
];
|
||||
}
|
||||
|
||||
@ -47,6 +50,9 @@ export function allocStylingContext(
|
||||
// each instance gets a copy
|
||||
const context = templateStyleContext.slice() as any as StylingContext;
|
||||
context[StylingIndex.ElementPosition] = element;
|
||||
|
||||
// this will prevent any other directives from extending the context
|
||||
context[StylingIndex.MasterFlagPosition] |= StylingFlags.BindingAllocationLocked;
|
||||
return context;
|
||||
}
|
||||
|
||||
@ -89,8 +95,8 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
|
||||
|
||||
export function isStylingContext(value: any): value is StylingContext {
|
||||
// Not an LView or an LContainer
|
||||
return Array.isArray(value) && typeof value[FLAGS] !== 'number' &&
|
||||
typeof value[ACTIVE_INDEX] !== 'number';
|
||||
return Array.isArray(value) && typeof value[StylingIndex.MasterFlagPosition] === 'number' &&
|
||||
Array.isArray(value[StylingIndex.InitialStyleValuesPosition]);
|
||||
}
|
||||
|
||||
export function isAnimationProp(name: string): boolean {
|
||||
@ -182,3 +188,15 @@ export function allocPlayerContext(data: StylingContext): PlayerContext {
|
||||
export function throwInvalidRefError() {
|
||||
throw new Error('Only elements that exist in an Angular application can be used for animations');
|
||||
}
|
||||
|
||||
export function hasStyling(attrs: TAttributes): boolean {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
if (attr == AttributeMarker.Classes || attr == AttributeMarker.Styles) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasClassInput(tNode: TNode) {
|
||||
return tNode.flags & TNodeFlags.hasClassInput ? true : false;
|
||||
}
|
||||
|
@ -9,17 +9,15 @@
|
||||
import {global} from '../util';
|
||||
|
||||
import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from './assert';
|
||||
import {ACTIVE_INDEX, LContainer} from './interfaces/container';
|
||||
import {ACTIVE_INDEX, LCONTAINER_LENGTH, LContainer} from './interfaces/container';
|
||||
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
|
||||
import {ComponentDef, DirectiveDef} from './interfaces/definition';
|
||||
import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector';
|
||||
import {TContainerNode, TElementNode, TNode, TNodeFlags} from './interfaces/node';
|
||||
import {TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {RComment, RElement, RText} from './interfaces/renderer';
|
||||
import {StylingContext} from './interfaces/styling';
|
||||
import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, PARENT, RootContext, TData, TVIEW, TView} from './interfaces/view';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the values are different from a change detection stand point.
|
||||
*
|
||||
@ -127,7 +125,7 @@ export function isComponentDef<T>(def: DirectiveDef<T>): def is ComponentDef<T>
|
||||
|
||||
export function isLContainer(value: RElement | RComment | LContainer | StylingContext): boolean {
|
||||
// Styling contexts are also arrays, but their first index contains an element node
|
||||
return Array.isArray(value) && typeof value[ACTIVE_INDEX] === 'number';
|
||||
return Array.isArray(value) && value.length === LCONTAINER_LENGTH;
|
||||
}
|
||||
|
||||
export function isRootView(target: LView): boolean {
|
||||
@ -260,3 +258,33 @@ export function addAllToArray(items: any[], arr: any[]) {
|
||||
arr.push(items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a current view, finds the nearest component's host (LElement).
|
||||
*
|
||||
* @param lView LView for which we want a host element node
|
||||
* @param declarationMode indicates whether DECLARATION_VIEW or PARENT should be used to climb the
|
||||
* tree.
|
||||
* @returns The host node
|
||||
*/
|
||||
export function findComponentView(lView: LView, declarationMode?: boolean): LView {
|
||||
let rootTNode = lView[HOST_NODE];
|
||||
|
||||
while (rootTNode && rootTNode.type === TNodeType.View) {
|
||||
ngDevMode && assertDefined(
|
||||
lView[declarationMode ? DECLARATION_VIEW : PARENT],
|
||||
declarationMode ? 'lView.declarationView' : 'lView.parent');
|
||||
lView = lView[declarationMode ? DECLARATION_VIEW : PARENT] !;
|
||||
rootTNode = lView[HOST_NODE];
|
||||
}
|
||||
|
||||
return lView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the host TElementNode of the starting LView
|
||||
* @param lView the starting LView.
|
||||
*/
|
||||
export function getHostTElementNode(lView: LView): TElementNode|null {
|
||||
return findComponentView(lView, true)[HOST_NODE] as TElementNode;
|
||||
}
|
||||
|
@ -25,9 +25,9 @@ import {LQueries} from './interfaces/query';
|
||||
import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {CONTAINER_INDEX, CONTEXT, HOST_NODE, LView, QUERIES, RENDERER, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation';
|
||||
import {addRemoveViewFromContainer, appendChild, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation';
|
||||
import {getLView, getPreviousOrParentTNode} from './state';
|
||||
import {getComponentViewByIndex, getNativeByTNode, getParentInjectorTNode, getParentInjectorView, hasParentInjector, isComponent, isLContainer, isRootView} from './util';
|
||||
import {findComponentView, getComponentViewByIndex, getNativeByTNode, getParentInjectorTNode, getParentInjectorView, hasParentInjector, isComponent, isLContainer, isRootView} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
|
||||
@ -112,7 +112,7 @@ export function createTemplateRef<T>(
|
||||
if (container) {
|
||||
insertView(lView, container, hostView !, index !, hostTNode !.index);
|
||||
}
|
||||
renderEmbeddedTemplate(lView, this._tView, context, RenderFlags.Create);
|
||||
renderEmbeddedTemplate(lView, this._tView, context);
|
||||
const viewRef = new ViewRef(lView, context, -1);
|
||||
viewRef._tViewNode = lView[HOST_NODE] as TViewNode;
|
||||
return viewRef;
|
||||
|
@ -11,7 +11,7 @@ import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detec
|
||||
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
|
||||
|
||||
import {checkNoChanges, checkNoChangesInRootView, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
|
||||
import {checkNoChanges, checkNoChangesInRootView, checkView, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
|
||||
import {TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {FLAGS, HOST, HOST_NODE, LView, LViewFlags, PARENT, RENDERER_FACTORY} from './interfaces/view';
|
||||
import {destroyLView} from './node_manipulation';
|
||||
@ -244,16 +244,7 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
||||
*
|
||||
* See {@link ChangeDetectorRef#detach detach} for more information.
|
||||
*/
|
||||
detectChanges(): void {
|
||||
const rendererFactory = this._lView[RENDERER_FACTORY];
|
||||
if (rendererFactory.begin) {
|
||||
rendererFactory.begin();
|
||||
}
|
||||
detectChangesInternal(this._lView, this.context, null);
|
||||
if (rendererFactory.end) {
|
||||
rendererFactory.end();
|
||||
}
|
||||
}
|
||||
detectChanges(): void { detectChangesInternal(this._lView, this.context); }
|
||||
|
||||
/**
|
||||
* Checks the change detector and its children, and throws if any changes are detected.
|
||||
|
@ -13,7 +13,7 @@ import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {ɵDomRendererFactory2} from '@angular/platform-browser';
|
||||
import {ANIMATION_MODULE_TYPE, BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {fixmeIvy, ivyEnabled} from '@angular/private/testing';
|
||||
|
||||
const DEFAULT_NAMESPACE_ID = 'id';
|
||||
const DEFAULT_COMPONENT_ID = '1';
|
||||
@ -3110,6 +3110,10 @@ const DEFAULT_COMPONENT_ID = '1';
|
||||
expect(element.style['height']).toEqual(height);
|
||||
}
|
||||
|
||||
// In Ivy, change detection needs to run before the ViewQuery for cmp.element will
|
||||
// resolve. Keeping this test enabled since we still want to test the animation logic.
|
||||
if (ivyEnabled) fixture.detectChanges();
|
||||
|
||||
const cmp = fixture.componentInstance;
|
||||
const element = cmp.element.nativeElement;
|
||||
fixture.detectChanges();
|
||||
|
@ -14,7 +14,7 @@ import {CommonModule} from '@angular/common';
|
||||
import {Component, HostBinding, ViewChild} from '@angular/core';
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {fixmeIvy, ivyEnabled} from '@angular/private/testing';
|
||||
|
||||
import {HostListener} from '../../src/metadata/directives';
|
||||
|
||||
@ -1706,6 +1706,11 @@ import {HostListener} from '../../src/metadata/directives';
|
||||
TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp]});
|
||||
const fixture = TestBed.createComponent(ParentCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
// In Ivy, change detection needs to run before the ViewQuery for cmp.child will resolve.
|
||||
// Keeping this test enabled since we still want to test the animation logic in Ivy.
|
||||
if (ivyEnabled) fixture.detectChanges();
|
||||
|
||||
cmp.child.items = [4, 5, 6];
|
||||
fixture.detectChanges();
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {AnimationGroupPlayer} from '@angular/animations/src/players/animation_gr
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {ivyEnabled} from '@angular/private/testing';
|
||||
|
||||
import {TestBed} from '../../testing';
|
||||
|
||||
@ -290,6 +291,10 @@ import {TestBed} from '../../testing';
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
// In Ivy, change detection needs to run before the ViewQuery for cmp.element will resolve.
|
||||
// Keeping this test enabled since we still want to test the animation logic in Ivy.
|
||||
if (ivyEnabled) fixture.detectChanges();
|
||||
|
||||
const elm = cmp.element.nativeElement;
|
||||
const foo = elm.querySelector('.foo') as HTMLElement;
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {fixmeIvy, ivyEnabled, modifiedInIvy} from '@angular/private/testing';
|
||||
|
||||
import {NoopNgZone} from '../src/zone/ng_zone';
|
||||
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
|
||||
@ -421,6 +421,11 @@ class SomeComponent {
|
||||
it('should detach attached embedded views if they are destroyed', () => {
|
||||
const comp = TestBed.createComponent(EmbeddedViewComp);
|
||||
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
|
||||
|
||||
// In Ivy, change detection needs to run before the ViewQuery for tplRef will resolve.
|
||||
// Keeping this test enabled since we still want to test this destroy logic in Ivy.
|
||||
if (ivyEnabled) comp.detectChanges();
|
||||
|
||||
const embeddedViewRef = comp.componentInstance.tplRef.createEmbeddedView({});
|
||||
|
||||
appRef.attachView(embeddedViewRef);
|
||||
|
@ -9,7 +9,29 @@
|
||||
import '@angular/core/test/bundling/util/src/reflect_metadata';
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵbindPlayerFactory as bindPlayerFactory, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
import {Component, Directive, ElementRef, HostBinding, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵbindPlayerFactory as bindPlayerFactory, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[make-color-grey]',
|
||||
exportAs: 'makeColorGrey',
|
||||
host: {'style': 'font-family: Times New Roman;'}
|
||||
})
|
||||
class MakeColorGreyDirective {
|
||||
@HostBinding('style.background-color') private _backgroundColor: string|null = null;
|
||||
@HostBinding('style.color') private _textColor: string|null = null;
|
||||
|
||||
on() {
|
||||
this._backgroundColor = 'grey';
|
||||
this._textColor = 'black';
|
||||
}
|
||||
|
||||
off() {
|
||||
this._backgroundColor = null;
|
||||
this._textColor = null;
|
||||
}
|
||||
|
||||
toggle() { this._backgroundColor ? this.off() : this.on(); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'animation-world',
|
||||
@ -20,21 +42,40 @@ import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as P
|
||||
</nav>
|
||||
<div class="list">
|
||||
<div
|
||||
*ngFor="let item of items" class="record" [class]="makeClass(item)" style="border-radius: 10px"
|
||||
[style]="styles">
|
||||
{{ item }}
|
||||
#makeColorGrey="makeColorGrey"
|
||||
make-color-grey
|
||||
*ngFor="let item of items"
|
||||
class="record"
|
||||
[style.transform]="item.active ? 'scale(1.5)' : 'none'"
|
||||
[class]="makeClass(item)"
|
||||
style="border-radius: 10px"
|
||||
[style]="styles"
|
||||
[style.color]="item.value == 4 ? 'red' : null"
|
||||
[style.background-color]="item.value == 4 ? 'white' : null"
|
||||
(click)="toggleActive(item, makeColorGrey)">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class AnimationWorldComponent {
|
||||
items: any[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
items: any[] = [
|
||||
{value: 1, active: false}, {value: 2, active: false}, {value: 3, active: false},
|
||||
{value: 4, active: false}, {value: 5, active: false}, {value: 6, active: false},
|
||||
{value: 7, active: false}, {value: 8, active: false}, {value: 9, active: false}
|
||||
];
|
||||
private _hostElement: HTMLElement;
|
||||
public styles: {[key: string]: any}|null = null;
|
||||
|
||||
constructor(element: ElementRef) { this._hostElement = element.nativeElement; }
|
||||
|
||||
makeClass(index: number) { return `record-${index}`; }
|
||||
makeClass(item: any) { return `record-${item.value}`; }
|
||||
|
||||
toggleActive(item: any, makeColorGrey: MakeColorGreyDirective) {
|
||||
item.active = !item.active;
|
||||
makeColorGrey.toggle();
|
||||
markDirty(this);
|
||||
}
|
||||
|
||||
animateWithStyles() {
|
||||
this.styles = animateStyleFactory([{opacity: 0}, {opacity: 1}], 300, 'ease-out');
|
||||
@ -52,7 +93,8 @@ class AnimationWorldComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [AnimationWorldComponent], imports: [CommonModule]})
|
||||
@NgModule(
|
||||
{declarations: [AnimationWorldComponent, MakeColorGreyDirective], imports: [CommonModule]})
|
||||
class AnimationWorldModule {
|
||||
}
|
||||
|
||||
|
@ -27,10 +27,10 @@
|
||||
"name": "DECLARATION_VIEW"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY"
|
||||
"name": "EMPTY_ARRAY"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY_ARRAY"
|
||||
"name": "EMPTY_OBJ"
|
||||
},
|
||||
{
|
||||
"name": "EmptyErrorImpl"
|
||||
@ -170,6 +170,9 @@
|
||||
{
|
||||
"name": "checkNoChangesMode"
|
||||
},
|
||||
{
|
||||
"name": "checkView"
|
||||
},
|
||||
{
|
||||
"name": "componentRefresh"
|
||||
},
|
||||
@ -209,9 +212,6 @@
|
||||
{
|
||||
"name": "defineComponent"
|
||||
},
|
||||
{
|
||||
"name": "detectChangesInternal"
|
||||
},
|
||||
{
|
||||
"name": "diPublicInInjector"
|
||||
},
|
||||
@ -254,9 +254,6 @@
|
||||
{
|
||||
"name": "getContainerRenderParent"
|
||||
},
|
||||
{
|
||||
"name": "getCreationMode"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveDef"
|
||||
},
|
||||
@ -353,6 +350,9 @@
|
||||
{
|
||||
"name": "isComponentDef"
|
||||
},
|
||||
{
|
||||
"name": "isCreationMode"
|
||||
},
|
||||
{
|
||||
"name": "isFactory"
|
||||
},
|
||||
|
@ -45,10 +45,10 @@
|
||||
"name": "DefaultIterableDifferFactory"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY"
|
||||
"name": "EMPTY_ARRAY"
|
||||
},
|
||||
{
|
||||
"name": "EMPTY_ARRAY"
|
||||
"name": "EMPTY_OBJ"
|
||||
},
|
||||
{
|
||||
"name": "ElementRef"
|
||||
@ -86,6 +86,9 @@
|
||||
{
|
||||
"name": "IterableDiffers"
|
||||
},
|
||||
{
|
||||
"name": "LCONTAINER_LENGTH"
|
||||
},
|
||||
{
|
||||
"name": "MONKEY_PATCH_KEY_NAME"
|
||||
},
|
||||
@ -320,21 +323,9 @@
|
||||
{
|
||||
"name": "_c18"
|
||||
},
|
||||
{
|
||||
"name": "_c19"
|
||||
},
|
||||
{
|
||||
"name": "_c2"
|
||||
},
|
||||
{
|
||||
"name": "_c20"
|
||||
},
|
||||
{
|
||||
"name": "_c21"
|
||||
},
|
||||
{
|
||||
"name": "_c22"
|
||||
},
|
||||
{
|
||||
"name": "_c3"
|
||||
},
|
||||
@ -371,6 +362,9 @@
|
||||
{
|
||||
"name": "_symbolIterator"
|
||||
},
|
||||
{
|
||||
"name": "_updateSingleStylingValue"
|
||||
},
|
||||
{
|
||||
"name": "addComponentLogic"
|
||||
},
|
||||
@ -389,6 +383,9 @@
|
||||
{
|
||||
"name": "allocStylingContext"
|
||||
},
|
||||
{
|
||||
"name": "allowValueChange"
|
||||
},
|
||||
{
|
||||
"name": "appendChild"
|
||||
},
|
||||
@ -437,6 +434,9 @@
|
||||
{
|
||||
"name": "checkNoChangesMode"
|
||||
},
|
||||
{
|
||||
"name": "checkView"
|
||||
},
|
||||
{
|
||||
"name": "cleanUpView"
|
||||
},
|
||||
@ -488,9 +488,6 @@
|
||||
{
|
||||
"name": "createRootContext"
|
||||
},
|
||||
{
|
||||
"name": "createStylingContextTemplate"
|
||||
},
|
||||
{
|
||||
"name": "createTNode"
|
||||
},
|
||||
@ -527,9 +524,6 @@
|
||||
{
|
||||
"name": "defineInjectable"
|
||||
},
|
||||
{
|
||||
"name": "delegateToClassInput"
|
||||
},
|
||||
{
|
||||
"name": "destroyLView"
|
||||
},
|
||||
@ -551,6 +545,9 @@
|
||||
{
|
||||
"name": "directiveInject"
|
||||
},
|
||||
{
|
||||
"name": "directiveOwnerPointers"
|
||||
},
|
||||
{
|
||||
"name": "domRendererFactory3"
|
||||
},
|
||||
@ -611,6 +608,9 @@
|
||||
{
|
||||
"name": "findDirectiveMatches"
|
||||
},
|
||||
{
|
||||
"name": "findOrPatchDirectiveIntoRegistry"
|
||||
},
|
||||
{
|
||||
"name": "findViaComponent"
|
||||
},
|
||||
@ -659,15 +659,21 @@
|
||||
{
|
||||
"name": "getContextLView"
|
||||
},
|
||||
{
|
||||
"name": "getCreationMode"
|
||||
},
|
||||
{
|
||||
"name": "getCurrentView"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveIndexFromEntry"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveIndexFromRegistry"
|
||||
},
|
||||
{
|
||||
"name": "getDirectiveRegistryValuesIndexOf"
|
||||
},
|
||||
{
|
||||
"name": "getElementDepthCount"
|
||||
},
|
||||
@ -683,9 +689,18 @@
|
||||
{
|
||||
"name": "getHostNative"
|
||||
},
|
||||
{
|
||||
"name": "getHostTElementNode"
|
||||
},
|
||||
{
|
||||
"name": "getInitialClassNameValue"
|
||||
},
|
||||
{
|
||||
"name": "getInitialIndex"
|
||||
},
|
||||
{
|
||||
"name": "getInitialStylingValuesIndexOf"
|
||||
},
|
||||
{
|
||||
"name": "getInitialValue"
|
||||
},
|
||||
@ -707,6 +722,9 @@
|
||||
{
|
||||
"name": "getLViewChild"
|
||||
},
|
||||
{
|
||||
"name": "getMatchingBindingIndex"
|
||||
},
|
||||
{
|
||||
"name": "getMultiOrSingleIndex"
|
||||
},
|
||||
@ -788,6 +806,9 @@
|
||||
{
|
||||
"name": "getRootView"
|
||||
},
|
||||
{
|
||||
"name": "getSinglePropIndexValue"
|
||||
},
|
||||
{
|
||||
"name": "getStyleSanitizer"
|
||||
},
|
||||
@ -813,19 +834,7 @@
|
||||
"name": "getValue"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementClassProp"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStyling"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStylingApply"
|
||||
},
|
||||
{
|
||||
"name": "hackSetStaticClasses"
|
||||
},
|
||||
{
|
||||
"name": "hackSquashDeclaration"
|
||||
"name": "hasClassInput"
|
||||
},
|
||||
{
|
||||
"name": "hasParentInjector"
|
||||
@ -833,6 +842,9 @@
|
||||
{
|
||||
"name": "hasPlayerBuilderChanged"
|
||||
},
|
||||
{
|
||||
"name": "hasStyling"
|
||||
},
|
||||
{
|
||||
"name": "hasTagAndTypeMatch"
|
||||
},
|
||||
@ -848,6 +860,9 @@
|
||||
{
|
||||
"name": "initNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "initializeStaticContext"
|
||||
},
|
||||
{
|
||||
"name": "initializeTNodeInputs"
|
||||
},
|
||||
@ -896,6 +911,9 @@
|
||||
{
|
||||
"name": "isContextDirty"
|
||||
},
|
||||
{
|
||||
"name": "isCreationMode"
|
||||
},
|
||||
{
|
||||
"name": "isCssClassMatching"
|
||||
},
|
||||
@ -905,6 +923,9 @@
|
||||
{
|
||||
"name": "isDifferent"
|
||||
},
|
||||
{
|
||||
"name": "isDirectiveDirty"
|
||||
},
|
||||
{
|
||||
"name": "isDirty"
|
||||
},
|
||||
@ -1077,7 +1098,13 @@
|
||||
"name": "renderEmbeddedTemplate"
|
||||
},
|
||||
{
|
||||
"name": "renderStyleAndClassBindings"
|
||||
"name": "renderInitialStylesAndClasses"
|
||||
},
|
||||
{
|
||||
"name": "renderInitialStylingValues"
|
||||
},
|
||||
{
|
||||
"name": "renderStyling"
|
||||
},
|
||||
{
|
||||
"name": "resetComponentState"
|
||||
@ -1121,6 +1148,9 @@
|
||||
{
|
||||
"name": "setCurrentDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "setDirectiveDirty"
|
||||
},
|
||||
{
|
||||
"name": "setDirty"
|
||||
},
|
||||
@ -1160,6 +1190,9 @@
|
||||
{
|
||||
"name": "setProp"
|
||||
},
|
||||
{
|
||||
"name": "setSanitizeFlag"
|
||||
},
|
||||
{
|
||||
"name": "setStyle"
|
||||
},
|
||||
@ -1209,7 +1242,7 @@
|
||||
"name": "updateClassProp"
|
||||
},
|
||||
{
|
||||
"name": "updateStyleProp"
|
||||
"name": "updateContextWithBindings"
|
||||
},
|
||||
{
|
||||
"name": "updateViewQuery"
|
||||
|
@ -1297,24 +1297,22 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
||||
|
||||
}));
|
||||
|
||||
fixmeIvy(
|
||||
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template')
|
||||
.it('Reattaches in the original cd mode', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<push-cmp></push-cmp>');
|
||||
const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0];
|
||||
cmp.changeDetectorRef.detach();
|
||||
cmp.changeDetectorRef.reattach();
|
||||
it('Reattaches in the original cd mode', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<push-cmp></push-cmp>');
|
||||
const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0];
|
||||
cmp.changeDetectorRef.detach();
|
||||
cmp.changeDetectorRef.reattach();
|
||||
|
||||
// renderCount should NOT be incremented with each CD as CD mode
|
||||
// should be resetted to
|
||||
// on-push
|
||||
ctx.detectChanges();
|
||||
expect(cmp.renderCount).toBeGreaterThan(0);
|
||||
const count = cmp.renderCount;
|
||||
// renderCount should NOT be incremented with each CD as CD mode
|
||||
// should be resetted to
|
||||
// on-push
|
||||
ctx.detectChanges();
|
||||
expect(cmp.renderCount).toBeGreaterThan(0);
|
||||
const count = cmp.renderCount;
|
||||
|
||||
ctx.detectChanges();
|
||||
expect(cmp.renderCount).toBe(count);
|
||||
}));
|
||||
ctx.detectChanges();
|
||||
expect(cmp.renderCount).toBe(count);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
@ -22,7 +22,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import {dispatchEvent, el} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {fixmeIvy, modifiedInIvy} from '@angular/private/testing';
|
||||
import {fixmeIvy, modifiedInIvy, obsoleteInIvy} from '@angular/private/testing';
|
||||
|
||||
import {stringify} from '../../src/util';
|
||||
|
||||
@ -412,8 +412,9 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
const ngIfEl = fixture.debugElement.children[0];
|
||||
const someViewport: SomeViewport =
|
||||
ngIfEl.childNodes
|
||||
.find(debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE)
|
||||
.injector.get(SomeViewport);
|
||||
.find(
|
||||
debugElement => debugElement.nativeNode.nodeType ===
|
||||
Node.COMMENT_NODE) !.injector.get(SomeViewport);
|
||||
expect(someViewport.container.length).toBe(2);
|
||||
expect(ngIfEl.children.length).toBe(2);
|
||||
|
||||
@ -541,7 +542,8 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(value.tagName.toLowerCase()).toEqual('div');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-709: Context discovery does not support templates (comment nodes)')
|
||||
fixmeIvy(
|
||||
'FW-870: DebugNode.references gets comment node instead of TemplateRef for template nodes')
|
||||
.it('should assign the TemplateRef to a user-defined variable', () => {
|
||||
const fixture =
|
||||
TestBed.configureTestingModule({declarations: [MyComp]})
|
||||
@ -583,47 +585,43 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
|
||||
describe('OnPush components', () => {
|
||||
|
||||
fixmeIvy(
|
||||
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template')
|
||||
.it('should use ChangeDetectorRef to manually request a check', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]});
|
||||
const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
it('should use ChangeDetectorRef to manually request a check', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]});
|
||||
const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
|
||||
const cmp = fixture.debugElement.children[0].references !['cmp'];
|
||||
const cmp = fixture.debugElement.children[0].references !['cmp'];
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
cmp.propagate();
|
||||
cmp.propagate();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
});
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template')
|
||||
.it('should be checked when its bindings got updated', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]});
|
||||
const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
it('should be checked when its bindings got updated', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]});
|
||||
const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
|
||||
const cmp = fixture.debugElement.children[0].references !['cmp'];
|
||||
const cmp = fixture.debugElement.children[0].references !['cmp'];
|
||||
|
||||
fixture.componentInstance.ctxProp = 'one';
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
fixture.componentInstance.ctxProp = 'one';
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
fixture.componentInstance.ctxProp = 'two';
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
});
|
||||
fixture.componentInstance.ctxProp = 'two';
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
});
|
||||
|
||||
if (getDOM().supportsDOMEvents()) {
|
||||
it('should allow to destroy a component from within a host event handler',
|
||||
@ -700,32 +698,29 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(cmp.prop).toEqual('two');
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template')
|
||||
.it('should be checked when an async pipe requests a check', fakeAsync(() => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
|
||||
const template = '<push-cmp-with-async #cmp></push-cmp-with-async>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
it('should be checked when an async pipe requests a check', fakeAsync(() => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
|
||||
const template = '<push-cmp-with-async #cmp></push-cmp-with-async>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
|
||||
tick();
|
||||
tick();
|
||||
|
||||
const cmp: PushCmpWithAsyncPipe =
|
||||
fixture.debugElement.children[0].references !['cmp'];
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references !['cmp'];
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
cmp.resolve(2);
|
||||
tick();
|
||||
cmp.resolve(2);
|
||||
tick();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
}));
|
||||
fixture.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should create a component that injects an @Host', () => {
|
||||
@ -812,7 +807,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
})
|
||||
.createComponent(MyComp);
|
||||
const tc = fixture.debugElement.childNodes.find(
|
||||
debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE);
|
||||
debugElement => debugElement.nativeNode.nodeType === Node.COMMENT_NODE) !;
|
||||
|
||||
const emitter = tc.injector.get(DirectiveEmittingEvent);
|
||||
const myComp = fixture.debugElement.injector.get(MyComp);
|
||||
@ -1327,24 +1322,21 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(comp.injectable).toBeAnInstanceOf(InjectableService);
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-804: Injection of view providers with the @Host annotation works differently in ivy')
|
||||
.it('should support viewProviders', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations:
|
||||
[MyComp, DirectiveProvidingInjectableInView, DirectiveConsumingInjectable],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
});
|
||||
const template = `
|
||||
it('should support viewProviders', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MyComp, DirectiveProvidingInjectableInView, DirectiveConsumingInjectable],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
});
|
||||
const template = `
|
||||
<directive-consuming-injectable #consuming>
|
||||
</directive-consuming-injectable>
|
||||
`;
|
||||
TestBed.overrideComponent(DirectiveProvidingInjectableInView, {set: {template}});
|
||||
const fixture = TestBed.createComponent(DirectiveProvidingInjectableInView);
|
||||
TestBed.overrideComponent(DirectiveProvidingInjectableInView, {set: {template}});
|
||||
const fixture = TestBed.createComponent(DirectiveProvidingInjectableInView);
|
||||
|
||||
const comp = fixture.debugElement.children[0].references !['consuming'];
|
||||
expect(comp.injectable).toBeAnInstanceOf(InjectableService);
|
||||
});
|
||||
const comp = fixture.debugElement.children[0].references !['consuming'];
|
||||
expect(comp.injectable).toBeAnInstanceOf(InjectableService);
|
||||
});
|
||||
|
||||
it('should support unbounded lookup', () => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -1508,7 +1500,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
}
|
||||
});
|
||||
|
||||
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
|
||||
obsoleteInIvy('DebugContext is not patched on exceptions in ivy')
|
||||
.it('should provide an error context when an error happens in DI', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MyComp, DirectiveThrowingAnError],
|
||||
@ -1527,7 +1519,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
}
|
||||
});
|
||||
|
||||
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
|
||||
obsoleteInIvy('DebugContext is not patched on exceptions in ivy')
|
||||
.it('should provide an error context when an error happens in change detection', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveThrowingAnError]});
|
||||
const template = `<input [value]="one.two.three" #local>`;
|
||||
@ -1546,7 +1538,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
}
|
||||
});
|
||||
|
||||
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
|
||||
obsoleteInIvy('DebugContext is not patched on exceptions in ivy')
|
||||
.it('should provide an error context when an error happens in change detection (text node)',
|
||||
() => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
@ -1562,35 +1554,33 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
}
|
||||
});
|
||||
|
||||
if (getDOM().supportsDOMEvents()) { // this is required to use fakeAsync
|
||||
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
|
||||
.it('should provide an error context when an error happens in an event handler',
|
||||
fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
});
|
||||
const template = `<span emitter listener (event)="throwError()" #local></span>`;
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
tick();
|
||||
obsoleteInIvy('DebugContext is not patched on exceptions in ivy')
|
||||
.it('should provide an error context when an error happens in an event handler',
|
||||
fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
});
|
||||
const template = `<span emitter listener (event)="throwError()" #local></span>`;
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
tick();
|
||||
|
||||
const tc = fixture.debugElement.children[0];
|
||||
const tc = fixture.debugElement.children[0];
|
||||
|
||||
const errorHandler = tc.injector.get(ErrorHandler);
|
||||
let err: any;
|
||||
spyOn(errorHandler, 'handleError').and.callFake((e: any) => err = e);
|
||||
tc.injector.get(DirectiveEmittingEvent).fireEvent('boom');
|
||||
const errorHandler = tc.injector.get(ErrorHandler);
|
||||
let err: any;
|
||||
spyOn(errorHandler, 'handleError').and.callFake((e: any) => err = e);
|
||||
tc.injector.get(DirectiveEmittingEvent).fireEvent('boom');
|
||||
|
||||
expect(err).toBeTruthy();
|
||||
const c = getDebugContext(err);
|
||||
expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN');
|
||||
expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV');
|
||||
expect((<Injector>c.injector).get).toBeTruthy();
|
||||
expect(c.context).toBe(fixture.componentInstance);
|
||||
expect(c.references['local']).toBeDefined();
|
||||
}));
|
||||
}
|
||||
expect(err).toBeTruthy();
|
||||
const c = getDebugContext(err);
|
||||
expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN');
|
||||
expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV');
|
||||
expect((<Injector>c.injector).get).toBeTruthy();
|
||||
expect(c.context).toBe(fixture.componentInstance);
|
||||
expect(c.references['local']).toBeDefined();
|
||||
}));
|
||||
});
|
||||
|
||||
it('should support imperative views', () => {
|
||||
|
@ -182,7 +182,7 @@ describe('projection', () => {
|
||||
expect(main.nativeElement).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-796: Content projection logic is incorrect for <ng-content> in nested templates')
|
||||
fixmeIvy('FW-833: Directive / projected node matching against class name')
|
||||
.it('should redistribute when the shadow dom changes', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalContentComponent, ManualViewportDirective]});
|
||||
@ -290,7 +290,7 @@ describe('projection', () => {
|
||||
expect(main.nativeElement).toHaveText('SIMPLE()START(A)END');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-796: Content projection logic is incorrect for <ng-content> in nested templates')
|
||||
fixmeIvy('FW-833: Directive / projected node matching against class name')
|
||||
.it('should support moving ng-content around', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ConditionalContentComponent, ProjectDirective, ManualViewportDirective]
|
||||
@ -430,7 +430,7 @@ describe('projection', () => {
|
||||
});
|
||||
}
|
||||
|
||||
fixmeIvy('FW-796: Content projection logic is incorrect for <ng-content> in nested templates')
|
||||
fixmeIvy('FW-869: debugElement.queryAllNodes returns nodes in the wrong order')
|
||||
.it('should support nested conditionals that contain ng-contents', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalTextComponent, ManualViewportDirective]});
|
||||
@ -474,7 +474,66 @@ describe('projection', () => {
|
||||
'<cmp-a2>a2<cmp-b21>b21</cmp-b21><cmp-b22>b22</cmp-b22></cmp-a2>');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-796: Content projection logic is incorrect for <ng-content> in nested templates')
|
||||
it('should project nodes into nested templates when the main template doesn\'t have <ng-content>',
|
||||
() => {
|
||||
|
||||
@Component({
|
||||
selector: 'content-in-template',
|
||||
template:
|
||||
`(<ng-template manual><ng-content select="[id=left]"></ng-content></ng-template>)`
|
||||
})
|
||||
class ContentInATemplateComponent {
|
||||
}
|
||||
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ContentInATemplateComponent, ManualViewportDirective]});
|
||||
TestBed.overrideComponent(
|
||||
MainComp,
|
||||
{set: {template: `<content-in-template><div id="left">A</div></content-in-template>`}});
|
||||
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('()');
|
||||
|
||||
let viewportElement =
|
||||
main.debugElement.queryAllNodes(By.directive(ManualViewportDirective))[0];
|
||||
viewportElement.injector.get(ManualViewportDirective).show();
|
||||
expect(main.nativeElement).toHaveText('(A)');
|
||||
});
|
||||
|
||||
it('should project nodes into nested templates and the main template', () => {
|
||||
|
||||
@Component({
|
||||
selector: 'content-in-main-and-template',
|
||||
template:
|
||||
`<ng-content></ng-content>(<ng-template manual><ng-content select="[id=left]"></ng-content></ng-template>)`
|
||||
})
|
||||
class ContentInMainAndTemplateComponent {
|
||||
}
|
||||
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ContentInMainAndTemplateComponent, ManualViewportDirective]});
|
||||
TestBed.overrideComponent(MainComp, {
|
||||
set: {
|
||||
template:
|
||||
`<content-in-main-and-template><div id="left">A</div>B</content-in-main-and-template>`
|
||||
}
|
||||
});
|
||||
|
||||
const main = TestBed.createComponent(MainComp);
|
||||
|
||||
main.detectChanges();
|
||||
expect(main.nativeElement).toHaveText('B()');
|
||||
|
||||
let viewportElement = main.debugElement.queryAllNodes(By.directive(ManualViewportDirective))[0];
|
||||
viewportElement.injector.get(ManualViewportDirective).show();
|
||||
expect(main.nativeElement).toHaveText('B(A)');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-833: Directive / projected node matching against class name')
|
||||
.it('should project filled view containers into a view container', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ConditionalContentComponent, ManualViewportDirective]});
|
||||
|
@ -112,23 +112,21 @@ describe('Query API', () => {
|
||||
expect(directive.child.text).toEqual('foo');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
.it('should contain the first view child', () => {
|
||||
const template = '<needs-view-child #q></needs-view-child>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
it('should contain the first view child', () => {
|
||||
const template = '<needs-view-child #q></needs-view-child>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
|
||||
q.shouldShow = false;
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([
|
||||
['setter', 'foo'], ['init', 'foo'], ['check', 'foo'], ['setter', null],
|
||||
['check', null]
|
||||
]);
|
||||
});
|
||||
q.shouldShow = false;
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([
|
||||
['setter', 'foo'], ['init', 'foo'], ['check', 'foo'], ['setter', null], ['check', null]
|
||||
]);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
modifiedInIvy('Static ViewChild and ContentChild queries are resolved in update mode')
|
||||
.it('should set static view and content children already after the constructor call', () => {
|
||||
const template =
|
||||
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';
|
||||
@ -142,34 +140,33 @@ describe('Query API', () => {
|
||||
expect(q.viewChild.text).toEqual('viewFoo');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
.it('should contain the first view child across embedded views', () => {
|
||||
TestBed.overrideComponent(
|
||||
MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}});
|
||||
TestBed.overrideComponent(NeedsViewChild, {
|
||||
set: {
|
||||
template:
|
||||
'<div *ngIf="true"><div *ngIf="shouldShow" text="foo"></div></div><div *ngIf="shouldShow2" text="bar"></div>'
|
||||
}
|
||||
});
|
||||
const view = TestBed.createComponent(MyComp0);
|
||||
it('should contain the first view child across embedded views', () => {
|
||||
TestBed.overrideComponent(
|
||||
MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}});
|
||||
TestBed.overrideComponent(NeedsViewChild, {
|
||||
set: {
|
||||
template:
|
||||
'<div *ngIf="true"><div *ngIf="shouldShow" text="foo"></div></div><div *ngIf="shouldShow2" text="bar"></div>'
|
||||
}
|
||||
});
|
||||
const view = TestBed.createComponent(MyComp0);
|
||||
|
||||
view.detectChanges();
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
view.detectChanges();
|
||||
const q: NeedsViewChild = view.debugElement.children[0].references !['q'];
|
||||
expect(q.logs).toEqual([['setter', 'foo'], ['init', 'foo'], ['check', 'foo']]);
|
||||
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = true;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', 'bar'], ['check', 'bar']]);
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = true;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', 'bar'], ['check', 'bar']]);
|
||||
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = false;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', null], ['check', null]]);
|
||||
});
|
||||
q.shouldShow = false;
|
||||
q.shouldShow2 = false;
|
||||
q.logs = [];
|
||||
view.detectChanges();
|
||||
expect(q.logs).toEqual([['setter', null], ['check', null]]);
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
|
||||
@ -589,31 +586,35 @@ describe('Query API', () => {
|
||||
});
|
||||
|
||||
// Note: this test is just document our current behavior, which we do for performance reasons.
|
||||
fixmeIvy('FW-782 - View queries are executed twice in some cases')
|
||||
.it('should not affected queries for projected templates if views are detached or moved', () => {
|
||||
const template =
|
||||
'<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>';
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
|
||||
expect(q.query.length).toBe(0);
|
||||
fixmeIvy('FW-853: Query results are cleared if embedded views are detached / moved')
|
||||
.it('should not affect queries for projected templates if views are detached or moved',
|
||||
() => {
|
||||
const template = `<manual-projecting #q>
|
||||
<ng-template let-x="x">
|
||||
<div [text]="x"></div>
|
||||
</ng-template>
|
||||
</manual-projecting>`;
|
||||
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
|
||||
expect(q.query.length).toBe(0);
|
||||
|
||||
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
|
||||
const view2 = q.vc.createEmbeddedView(q.template, {'x': '2'});
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
|
||||
const view2 = q.vc.createEmbeddedView(q.template, {'x': '2'});
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
|
||||
q.vc.detach(1);
|
||||
q.vc.detach(0);
|
||||
q.vc.detach(1);
|
||||
q.vc.detach(0);
|
||||
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
|
||||
q.vc.insert(view2);
|
||||
q.vc.insert(view1);
|
||||
q.vc.insert(view2);
|
||||
q.vc.insert(view1);
|
||||
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
});
|
||||
view.detectChanges();
|
||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should remove manually projected templates if their parent view is destroyed', () => {
|
||||
|
@ -725,16 +725,14 @@ class TestComp {
|
||||
expect(d.dependency).toBeNull();
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should instantiate directives that depends on the host component', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [SimpleComponent, NeedsComponentFromHost]});
|
||||
TestBed.overrideComponent(
|
||||
SimpleComponent, {set: {template: '<div needsComponentFromHost></div>'}});
|
||||
const el = createComponent('<div simpleComponent></div>');
|
||||
const d = el.children[0].children[0].injector.get(NeedsComponentFromHost);
|
||||
expect(d.dependency).toBeAnInstanceOf(SimpleComponent);
|
||||
});
|
||||
it('should instantiate directives that depends on the host component', () => {
|
||||
TestBed.configureTestingModule({declarations: [SimpleComponent, NeedsComponentFromHost]});
|
||||
TestBed.overrideComponent(
|
||||
SimpleComponent, {set: {template: '<div needsComponentFromHost></div>'}});
|
||||
const el = createComponent('<div simpleComponent></div>');
|
||||
const d = el.children[0].children[0].injector.get(NeedsComponentFromHost);
|
||||
expect(d.dependency).toBeAnInstanceOf(SimpleComponent);
|
||||
});
|
||||
|
||||
fixmeIvy('unknown').it(
|
||||
'should instantiate host views for components that have a @Host dependency ', () => {
|
||||
|
@ -87,73 +87,6 @@ describe('change detection', () => {
|
||||
await whenRendered(myComp);
|
||||
expect(getRenderedText(myComp)).toEqual('updated');
|
||||
}));
|
||||
|
||||
it('should support detectChanges on components that have LContainers', () => {
|
||||
let structuralComp !: StructuralComp;
|
||||
|
||||
function FooTemplate(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, bind(ctx.value));
|
||||
}
|
||||
}
|
||||
|
||||
class StructuralComp {
|
||||
tmp !: TemplateRef<any>;
|
||||
value = 'one';
|
||||
|
||||
constructor(public vcr: ViewContainerRef) {}
|
||||
|
||||
create() { return this.vcr.createEmbeddedView(this.tmp, this); }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: StructuralComp,
|
||||
selectors: [['structural-comp']],
|
||||
factory: () => structuralComp =
|
||||
new StructuralComp(directiveInject(ViewContainerRef as any)),
|
||||
inputs: {tmp: 'tmp'},
|
||||
consts: 1,
|
||||
vars: 1,
|
||||
template: FooTemplate
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <ng-template #foo>{{ value }}</ng-template>
|
||||
* <structural-comp [tmp]="foo"></structural-comp>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
template(0, FooTemplate, 2, 1, 'ng-template', null, ['foo', ''], templateRefExtractor);
|
||||
element(2, 'structural-comp');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
const foo = reference(1) as any;
|
||||
elementProperty(2, 'tmp', bind(foo));
|
||||
}
|
||||
}, 3, 1, [StructuralComp]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>');
|
||||
|
||||
const viewRef: EmbeddedViewRef<any> = structuralComp.create();
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>one');
|
||||
|
||||
// check embedded view update
|
||||
structuralComp.value = 'two';
|
||||
viewRef.detectChanges();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>two');
|
||||
|
||||
// check root view update
|
||||
structuralComp.value = 'three';
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<structural-comp>three</structural-comp>three');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('onPush', () => {
|
||||
@ -662,6 +595,117 @@ describe('change detection', () => {
|
||||
expect(getRenderedText(comp)).toEqual('1');
|
||||
});
|
||||
|
||||
describe('dynamic views', () => {
|
||||
let structuralComp: StructuralComp|null = null;
|
||||
|
||||
beforeEach(() => structuralComp = null);
|
||||
|
||||
class StructuralComp {
|
||||
tmp !: TemplateRef<any>;
|
||||
value = 'one';
|
||||
|
||||
constructor(public vcr: ViewContainerRef) {}
|
||||
|
||||
create() { return this.vcr.createEmbeddedView(this.tmp, this); }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: StructuralComp,
|
||||
selectors: [['structural-comp']],
|
||||
factory: () => structuralComp =
|
||||
new StructuralComp(directiveInject(ViewContainerRef as any)),
|
||||
inputs: {tmp: 'tmp'},
|
||||
consts: 1,
|
||||
vars: 1,
|
||||
template: function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, bind(ctx.value));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('should support ViewRef.detectChanges()', () => {
|
||||
function FooTemplate(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, bind(ctx.value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <ng-template #foo>{{ value }}</ng-template>
|
||||
* <structural-comp [tmp]="foo"></structural-comp>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
template(
|
||||
0, FooTemplate, 1, 1, 'ng-template', null, ['foo', ''], templateRefExtractor);
|
||||
element(2, 'structural-comp');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
const foo = reference(1) as any;
|
||||
elementProperty(2, 'tmp', bind(foo));
|
||||
}
|
||||
}, 3, 1, [StructuralComp]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>');
|
||||
|
||||
const viewRef: EmbeddedViewRef<any> = structuralComp !.create();
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>one');
|
||||
|
||||
// check embedded view update
|
||||
structuralComp !.value = 'two';
|
||||
viewRef.detectChanges();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>two');
|
||||
|
||||
// check root view update
|
||||
structuralComp !.value = 'three';
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<structural-comp>three</structural-comp>three');
|
||||
});
|
||||
|
||||
it('should support ViewRef.detectChanges() directly after creation', () => {
|
||||
function FooTemplate(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0, 'Template text');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <ng-template #foo>Template text</ng-template>
|
||||
* <structural-comp [tmp]="foo"></structural-comp>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
template(
|
||||
0, FooTemplate, 1, 0, 'ng-template', null, ['foo', ''], templateRefExtractor);
|
||||
element(2, 'structural-comp');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
const foo = reference(1) as any;
|
||||
elementProperty(2, 'tmp', bind(foo));
|
||||
}
|
||||
}, 3, 1, [StructuralComp]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>');
|
||||
|
||||
const viewRef: EmbeddedViewRef<any> = structuralComp !.create();
|
||||
viewRef.detectChanges();
|
||||
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>Template text');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('attach/detach', () => {
|
||||
|
@ -498,6 +498,8 @@ describe('recursive components', () => {
|
||||
class NgIfTree {
|
||||
data: TreeNode = _buildTree(0);
|
||||
|
||||
ngDoCheck() { events.push('check' + this.data.value); }
|
||||
|
||||
ngOnDestroy() { events.push('destroy' + this.data.value); }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
@ -628,6 +630,7 @@ describe('recursive components', () => {
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(getRenderedText(fixture.component)).toEqual('6201534');
|
||||
expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']);
|
||||
|
||||
events = [];
|
||||
fixture.component.skipContent = true;
|
||||
|
@ -38,8 +38,8 @@ describe('JS control flow', () => {
|
||||
embeddedViewEnd();
|
||||
}
|
||||
}
|
||||
containerRefreshEnd();
|
||||
}
|
||||
containerRefreshEnd();
|
||||
}, 2);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
|
@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Attribute, ChangeDetectorRef, ElementRef, Host, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, createInjector, defineInjectable, defineInjector} from '@angular/core';
|
||||
import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
||||
import {Attribute, ChangeDetectorRef, ElementRef, Host, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, createInjector, defineInjectable, defineInjector} from '@angular/core';
|
||||
import {ComponentType, RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
import {defineComponent} from '../../src/render3/definition';
|
||||
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
|
||||
@ -1066,8 +1066,12 @@ describe('di', () => {
|
||||
|
||||
describe('@Host', () => {
|
||||
let dirA: DirA|null = null;
|
||||
let dirString: DirString|null = null;
|
||||
|
||||
beforeEach(() => dirA = null);
|
||||
beforeEach(() => {
|
||||
dirA = null;
|
||||
dirString = null;
|
||||
});
|
||||
|
||||
class DirA {
|
||||
constructor(@Host() public dirB: DirB) {}
|
||||
@ -1079,13 +1083,93 @@ describe('di', () => {
|
||||
});
|
||||
}
|
||||
|
||||
it('should not find providers across component boundaries', () => {
|
||||
class DirString {
|
||||
constructor(@Host() public s: String) {}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: DirString,
|
||||
selectors: [['', 'dirString', '']],
|
||||
factory: () => dirString = new DirString(directiveInject(String, InjectFlags.Host))
|
||||
});
|
||||
}
|
||||
|
||||
it('should find viewProviders on the host itself', () => {
|
||||
/** <div dirString></div> */
|
||||
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['dirString', '']);
|
||||
}
|
||||
}, 1, 0, [DirString], [], null, [], [{provide: String, useValue: 'Foo'}]);
|
||||
|
||||
/* <comp></comp> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'comp');
|
||||
}
|
||||
}, 1, 0, [Comp]);
|
||||
|
||||
new ComponentFixture(App);
|
||||
expect(dirString !.s).toEqual('Foo');
|
||||
});
|
||||
|
||||
it('should find host component on the host itself', () => {
|
||||
let dirComp: DirComp|null = null;
|
||||
|
||||
class DirComp {
|
||||
constructor(@Host() public comp: any) {}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: DirComp,
|
||||
selectors: [['', 'dirCmp', '']],
|
||||
factory: () => dirComp = new DirComp(directiveInject(Comp, InjectFlags.Host))
|
||||
});
|
||||
}
|
||||
|
||||
/** <div dirCmp></div> */
|
||||
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['dirCmp', '']);
|
||||
}
|
||||
}, 1, 0, [DirComp]);
|
||||
|
||||
/* <comp></comp> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'comp');
|
||||
}
|
||||
}, 1, 0, [Comp]);
|
||||
|
||||
new ComponentFixture(App);
|
||||
expect(dirComp !.comp instanceof Comp).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not find providers on the host itself', () => {
|
||||
/** <div dirString></div> */
|
||||
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['dirString', '']);
|
||||
}
|
||||
}, 1, 0, [DirString], [], null, [{provide: String, useValue: 'Foo'}]);
|
||||
|
||||
/* <comp></comp> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'comp');
|
||||
}
|
||||
}, 1, 0, [Comp]);
|
||||
|
||||
expect(() => {
|
||||
new ComponentFixture(App);
|
||||
}).toThrowError(/NodeInjector: NOT_FOUND \[String\]/);
|
||||
});
|
||||
|
||||
it('should not find other directives on the host itself', () => {
|
||||
/** <div dirA></div> */
|
||||
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['dirA', '']);
|
||||
}
|
||||
}, 1, 0, [DirA, DirB]);
|
||||
}, 1, 0, [DirA]);
|
||||
|
||||
/* <comp dirB></comp> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
@ -1099,7 +1183,7 @@ describe('di', () => {
|
||||
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
|
||||
});
|
||||
|
||||
it('should not find providers across component boundaries if in inline view', () => {
|
||||
it('should not find providers on the host itself if in inline view', () => {
|
||||
let comp !: any;
|
||||
|
||||
/**
|
||||
@ -1177,6 +1261,154 @@ describe('di', () => {
|
||||
|
||||
expect(dirA !.dirB).toEqual(dirB);
|
||||
});
|
||||
|
||||
it('should not find component above the host', () => {
|
||||
let dirComp: DirComp|null = null;
|
||||
|
||||
class DirComp {
|
||||
constructor(@Host() public comp: any) {}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: DirComp,
|
||||
selectors: [['', 'dirCmp', '']],
|
||||
factory: () => dirComp = new DirComp(directiveInject(App, InjectFlags.Host))
|
||||
});
|
||||
}
|
||||
|
||||
/** <div dirCmp></div> */
|
||||
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['dirCmp', '']);
|
||||
}
|
||||
}, 1, 0, [DirComp]);
|
||||
|
||||
/* <comp></comp> */
|
||||
class App {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: App,
|
||||
selectors: [['app']],
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
factory: () => new App,
|
||||
template: function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'comp');
|
||||
}
|
||||
},
|
||||
directives: [Comp],
|
||||
});
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
new ComponentFixture(App);
|
||||
}).toThrowError(/NodeInjector: NOT_FOUND \[App\]/);
|
||||
});
|
||||
|
||||
describe('regression', () => {
|
||||
// based on https://stackblitz.com/edit/angular-riss8k?file=src/app/app.component.ts
|
||||
it('should allow directives with Host flag to inject view providers from containing component',
|
||||
() => {
|
||||
let controlContainers: ControlContainer[] = [];
|
||||
let injectedControlContainer: ControlContainer|null = null;
|
||||
|
||||
class ControlContainer {}
|
||||
|
||||
/*
|
||||
@Directive({
|
||||
selector: '[group]',
|
||||
providers: [{provide: ControlContainer, useExisting: GroupDirective}]
|
||||
})
|
||||
*/
|
||||
class GroupDirective {
|
||||
constructor() { controlContainers.push(this); }
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: GroupDirective,
|
||||
selectors: [['', 'group', '']],
|
||||
factory: () => new GroupDirective(),
|
||||
features: [ProvidersFeature(
|
||||
[{provide: ControlContainer, useExisting: GroupDirective}])],
|
||||
});
|
||||
}
|
||||
|
||||
// @Directive({selector: '[controlName]'})
|
||||
class ControlNameDirective {
|
||||
constructor(@Host() @SkipSelf() @Inject(ControlContainer) parent:
|
||||
ControlContainer) {
|
||||
injectedControlContainer = parent;
|
||||
}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: ControlNameDirective,
|
||||
selectors: [['', 'controlName', '']],
|
||||
factory: () => new ControlNameDirective(directiveInject(
|
||||
ControlContainer, InjectFlags.Host|InjectFlags.SkipSelf))
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
@Component({
|
||||
selector: 'child',
|
||||
template: `
|
||||
<input controlName type="text">
|
||||
`,
|
||||
viewProviders: [{provide: ControlContainer, useExisting: GroupDirective}]
|
||||
})
|
||||
*/
|
||||
class ChildComponent {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: ChildComponent,
|
||||
selectors: [['child']],
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
factory: () => new ChildComponent(),
|
||||
template: function(rf: RenderFlags, ctx: ChildComponent) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'input', ['controlName', '', 'type', 'text']);
|
||||
}
|
||||
},
|
||||
directives: [ControlNameDirective],
|
||||
features: [ProvidersFeature(
|
||||
[], [{provide: ControlContainer, useExisting: GroupDirective}])],
|
||||
});
|
||||
}
|
||||
/*
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
<div group>
|
||||
<child></child>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
*/
|
||||
class AppComponent {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: AppComponent,
|
||||
selectors: [['my-app']],
|
||||
consts: 2,
|
||||
vars: 0,
|
||||
factory: () => new AppComponent(),
|
||||
template: function(rf: RenderFlags, ctx: AppComponent) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', ['group', '']);
|
||||
element(1, 'child');
|
||||
elementEnd();
|
||||
}
|
||||
},
|
||||
directives: [ChildComponent, GroupDirective]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(AppComponent as ComponentType<AppComponent>);
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<div group=""><child><input controlname="" type="text"></child></div>');
|
||||
|
||||
expect(controlContainers).toEqual([injectedControlContainer !]);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -315,7 +315,7 @@ describe('discovery utils', () => {
|
||||
|
||||
it('should work on templates', () => {
|
||||
const templateComment = Array.from(fixture.hostElement.childNodes)
|
||||
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE);
|
||||
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE) !;
|
||||
const lContext = loadLContext(templateComment);
|
||||
expect(lContext).toBeDefined();
|
||||
expect(lContext.native as any).toBe(templateComment);
|
||||
@ -323,7 +323,7 @@ describe('discovery utils', () => {
|
||||
|
||||
it('should work on ICU expressions', () => {
|
||||
const icuComment = Array.from(fixture.hostElement.querySelector('i18n') !.childNodes)
|
||||
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE);
|
||||
.find((node: ChildNode) => node.nodeType === Node.COMMENT_NODE) !;
|
||||
const lContext = loadLContext(icuComment);
|
||||
expect(lContext).toBeDefined();
|
||||
expect(lContext.native as any).toBe(icuComment);
|
||||
@ -333,7 +333,7 @@ describe('discovery utils', () => {
|
||||
const ngContainerComment = Array.from(fixture.hostElement.childNodes)
|
||||
.find(
|
||||
(node: ChildNode) => node.nodeType === Node.COMMENT_NODE &&
|
||||
node.textContent === `ng-container`);
|
||||
node.textContent === `ng-container`) !;
|
||||
const lContext = loadLContext(ngContainerComment);
|
||||
expect(lContext).toBeDefined();
|
||||
expect(lContext.native as any).toBe(ngContainerComment);
|
||||
@ -542,7 +542,6 @@ describe('discovery utils deprecated', () => {
|
||||
});
|
||||
|
||||
it('should return a map of local refs for an element with styling context', () => {
|
||||
|
||||
class Comp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Comp,
|
||||
@ -554,7 +553,6 @@ describe('discovery utils deprecated', () => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
// <div #elRef class="fooClass">
|
||||
elementStart(0, 'div', null, ['elRef', '']);
|
||||
elementStyling(['fooClass']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation2, nextContext, reference, template, text, textBinding} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {NgIf} from './common_with_def';
|
||||
import {ComponentFixture, createComponent, renderToHtml} from './render_util';
|
||||
@ -206,8 +206,8 @@ describe('exports', () => {
|
||||
/** <div [class.red]="myInput.checked"</div> <input type="checkbox" checked #myInput> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
elementStyling([InitialStylingFlags.VALUES_MODE, 'red', true]);
|
||||
elementStart(0, 'div', [AttributeMarker.Classes, 'red']);
|
||||
elementStyling(['red']);
|
||||
elementEnd();
|
||||
element(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
|
||||
}
|
||||
|
@ -6,12 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ElementRef, EventEmitter} from '@angular/core';
|
||||
import {ElementRef} from '@angular/core';
|
||||
|
||||
import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature, NgOnChangesFeature, QueryList} from '../../src/render3/index';
|
||||
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
|
||||
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery, elementHostAttrs} from '../../src/render3/instructions';
|
||||
import {query, queryRefresh} from '../../src/render3/query';
|
||||
import {RenderFlags, InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {pureFunction1, pureFunction2} from '../../src/render3/pure_function';
|
||||
|
||||
import {ComponentFixture, TemplateFixture, createComponent, createDirective} from './render_util';
|
||||
@ -1141,9 +1141,8 @@ describe('host bindings', () => {
|
||||
vars: 0,
|
||||
hostBindings: (rf: RenderFlags, ctx: StaticHostClass, elIndex: number) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStyling(
|
||||
['mat-toolbar', InitialStylingFlags.VALUES_MODE, 'mat-toolbar', true], null, null,
|
||||
ctx);
|
||||
elementHostAttrs(ctx, [AttributeMarker.Classes, 'mat-toolbar']);
|
||||
elementStyling(['mat-toolbar'], null, null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStylingApply(0, ctx);
|
||||
@ -1164,6 +1163,5 @@ describe('host bindings', () => {
|
||||
const hostBindingEl = fixture.hostElement.querySelector('static-host-class') as HTMLElement;
|
||||
expect(hostBindingEl.className).toEqual('mat-toolbar');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -11,7 +11,6 @@ import {NgForOfContext} from '@angular/common';
|
||||
import {RenderFlags} from '../../src/render3';
|
||||
import {defineComponent} from '../../src/render3/definition';
|
||||
import {bind, element, elementAttribute, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap, interpolation1, renderTemplate, template, text, textBinding} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||
import {AttributeMarker} from '../../src/render3/interfaces/node';
|
||||
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass';
|
||||
import {defaultStyleSanitizer, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||
@ -28,10 +27,19 @@ describe('instructions', () => {
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
function createDiv(initialStyles?: (string | number)[], styleSanitizer?: StyleSanitizeFn) {
|
||||
elementStart(0, 'div');
|
||||
elementStyling(
|
||||
[], initialStyles && Array.isArray(initialStyles) ? initialStyles : null, styleSanitizer);
|
||||
function createDiv(
|
||||
initialClasses?: string[] | null, classBindingNames?: string[] | null,
|
||||
initialStyles?: string[] | null, styleBindingNames?: string[] | null,
|
||||
styleSanitizer?: StyleSanitizeFn) {
|
||||
const attrs: any[] = [];
|
||||
if (initialClasses) {
|
||||
attrs.push(AttributeMarker.Classes, ...initialClasses);
|
||||
}
|
||||
if (initialStyles) {
|
||||
attrs.push(AttributeMarker.Styles, ...initialStyles);
|
||||
}
|
||||
elementStart(0, 'div', attrs);
|
||||
elementStyling(classBindingNames || null, styleBindingNames || null, styleSanitizer);
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
@ -191,8 +199,9 @@ describe('instructions', () => {
|
||||
|
||||
describe('elementStyleProp', () => {
|
||||
it('should automatically sanitize unless a bypass operation is applied', () => {
|
||||
const t = new TemplateFixture(
|
||||
() => { return createDiv(['background-image'], defaultStyleSanitizer); }, () => {}, 1);
|
||||
const t = new TemplateFixture(() => {
|
||||
return createDiv(null, null, null, ['background-image'], defaultStyleSanitizer);
|
||||
}, () => {}, 1);
|
||||
t.update(() => {
|
||||
elementStyleProp(0, 0, 'url("http://server")');
|
||||
elementStylingApply(0);
|
||||
@ -211,8 +220,9 @@ describe('instructions', () => {
|
||||
it('should not re-apply the style value even if it is a newly bypassed again', () => {
|
||||
const sanitizerInterceptor = new MockSanitizerInterceptor();
|
||||
const t = createTemplateFixtureWithSanitizer(
|
||||
() => createDiv(['background-image'], sanitizerInterceptor.getStyleSanitizer()), 1,
|
||||
sanitizerInterceptor);
|
||||
() => createDiv(
|
||||
null, null, null, ['background-image'], sanitizerInterceptor.getStyleSanitizer()),
|
||||
1, sanitizerInterceptor);
|
||||
|
||||
t.update(() => {
|
||||
elementStyleProp(0, 0, bypassSanitizationTrustStyle('apple'));
|
||||
@ -232,8 +242,8 @@ describe('instructions', () => {
|
||||
|
||||
describe('elementStyleMap', () => {
|
||||
function createDivWithStyle() {
|
||||
elementStart(0, 'div');
|
||||
elementStyling([], ['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']);
|
||||
elementStart(0, 'div', [AttributeMarker.Styles, 'height', '10px']);
|
||||
elementStyling([], ['height']);
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
@ -251,7 +261,8 @@ describe('instructions', () => {
|
||||
const sanitizerInterceptor =
|
||||
new MockSanitizerInterceptor(value => { detectedValues.push(value); });
|
||||
const fixture = createTemplateFixtureWithSanitizer(
|
||||
() => createDiv([], sanitizerInterceptor.getStyleSanitizer()), 1, sanitizerInterceptor);
|
||||
() => createDiv(null, null, null, null, sanitizerInterceptor.getStyleSanitizer()), 1,
|
||||
sanitizerInterceptor);
|
||||
|
||||
fixture.update(() => {
|
||||
elementStylingMap(0, null, {
|
||||
|
@ -7,13 +7,13 @@
|
||||
*/
|
||||
|
||||
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {RendererType2} from '../../src/render/api';
|
||||
import {AttributeMarker, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index';
|
||||
|
||||
import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template, elementStylingMap, directiveInject} from '../../src/render3/instructions';
|
||||
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, elementStart, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, element, elementStyling, elementStylingApply, elementStyleProp, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, projection, projectionDef, reference, text, textBinding, template, elementStylingMap, directiveInject, elementHostAttrs} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||
import {NO_CHANGE} from '../../src/render3/tokens';
|
||||
import {HEADER_OFFSET, CONTEXT} from '../../src/render3/interfaces/view';
|
||||
import {enableBindings, disableBindings} from '../../src/render3/state';
|
||||
import {sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||
@ -115,9 +115,7 @@ describe('render3 integration test', () => {
|
||||
function Template(rf: RenderFlags, value: string) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, rf & RenderFlags.Create ? value : NO_CHANGE);
|
||||
textBinding(0, value);
|
||||
}
|
||||
}
|
||||
expect(renderToHtml(Template, 'once', 1, 1)).toEqual('once');
|
||||
@ -1414,7 +1412,6 @@ describe('render3 integration test', () => {
|
||||
});
|
||||
|
||||
describe('elementStyle', () => {
|
||||
|
||||
it('should support binding to styles', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
@ -1474,8 +1471,7 @@ describe('render3 integration test', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('elementClass', () => {
|
||||
|
||||
describe('class-based styling', () => {
|
||||
it('should support CSS class toggle', () => {
|
||||
/** <span [class.active]="class"></span> */
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
@ -1521,9 +1517,8 @@ describe('render3 integration test', () => {
|
||||
it('should work correctly with existing static classes', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
elementStyling(
|
||||
['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]);
|
||||
elementStart(0, 'span', [AttributeMarker.Classes, 'existing']);
|
||||
elementStyling(['existing', 'active']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
@ -1555,7 +1550,7 @@ describe('render3 integration test', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'my-comp');
|
||||
{ elementStyling(['active']); }
|
||||
elementStyling(['active']);
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
@ -1574,7 +1569,6 @@ describe('render3 integration test', () => {
|
||||
expect(fixture.html).toEqual('<my-comp class="">Comp Content</my-comp>');
|
||||
});
|
||||
|
||||
|
||||
it('should apply classes properly when nodes have LContainers', () => {
|
||||
let structuralComp !: StructuralComp;
|
||||
|
||||
@ -1658,17 +1652,17 @@ describe('render3 integration test', () => {
|
||||
set klass(value: string) { this.classesVal = value; }
|
||||
}
|
||||
|
||||
it('should delegate all initial classes to a [class] input binding if present on a directive on the same element',
|
||||
it('should delegate initial classes to a [class] input binding if present on a directive on the same element',
|
||||
() => {
|
||||
/**
|
||||
* <my-comp class="apple orange banana" DirWithClass></my-comp>
|
||||
* <div class="apple orange banana" DirWithClass></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', ['DirWithClass']);
|
||||
elementStyling([
|
||||
InitialStylingFlags.VALUES_MODE, 'apple', true, 'orange', true, 'banana', true
|
||||
]);
|
||||
elementStart(
|
||||
0, 'div',
|
||||
['DirWithClass', AttributeMarker.Classes, 'apple', 'orange', 'banana']);
|
||||
elementStyling();
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
@ -1683,7 +1677,7 @@ describe('render3 integration test', () => {
|
||||
it('should update `[class]` and bindings in the provided directive if the input is matched',
|
||||
() => {
|
||||
/**
|
||||
* <my-comp DirWithClass></my-comp>
|
||||
* <div DirWithClass></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
@ -1700,6 +1694,223 @@ describe('render3 integration test', () => {
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(mockClassDirective !.classesVal).toEqual('cucumber grape');
|
||||
});
|
||||
|
||||
it('should apply initial styling to the element that contains the directive with host styling',
|
||||
() => {
|
||||
class DirWithInitialStyling {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: DirWithInitialStyling,
|
||||
selectors: [['', 'DirWithInitialStyling', '']],
|
||||
factory: () => new DirWithInitialStyling(),
|
||||
hostBindings: function(
|
||||
rf: RenderFlags, ctx: DirWithInitialStyling, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementHostAttrs(ctx, [
|
||||
AttributeMarker.Classes, 'heavy', 'golden', AttributeMarker.Styles, 'color',
|
||||
'purple', 'font-weight', 'bold'
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public classesVal: string = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* <div DirWithInitialStyling
|
||||
* class="big"
|
||||
* style="color:black; * font-size:200px"></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', [
|
||||
'DirWithInitialStyling', '', AttributeMarker.Classes, 'big',
|
||||
AttributeMarker.Styles, 'color', 'black', 'font-size', '200px'
|
||||
]);
|
||||
}
|
||||
}, 1, 0, [DirWithInitialStyling]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
const target = fixture.hostElement.querySelector('div') !;
|
||||
const classes = target.getAttribute('class') !.split(/\s+/).sort();
|
||||
expect(classes).toEqual(['big', 'golden', 'heavy']);
|
||||
|
||||
expect(target.style.getPropertyValue('color')).toEqual('black');
|
||||
expect(target.style.getPropertyValue('font-size')).toEqual('200px');
|
||||
expect(target.style.getPropertyValue('font-weight')).toEqual('bold');
|
||||
});
|
||||
|
||||
it('should apply single styling bindings present within a directive onto the same element and defer the element\'s initial styling values when missing',
|
||||
() => {
|
||||
let dirInstance: DirWithSingleStylingBindings;
|
||||
/**
|
||||
* <DirWithInitialStyling class="def" [class.xyz] style="width:555px;" [style.width]
|
||||
* [style.height]></my-comp>
|
||||
*/
|
||||
class DirWithSingleStylingBindings {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: DirWithSingleStylingBindings,
|
||||
selectors: [['', 'DirWithSingleStylingBindings', '']],
|
||||
factory: () => dirInstance = new DirWithSingleStylingBindings(),
|
||||
hostBindings: function(
|
||||
rf: RenderFlags, ctx: DirWithSingleStylingBindings, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementHostAttrs(
|
||||
ctx,
|
||||
[AttributeMarker.Classes, 'def', AttributeMarker.Styles, 'width', '555px']);
|
||||
elementStyling(['xyz'], ['width', 'height'], null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(elementIndex, 0, ctx.width, null, ctx);
|
||||
elementStyleProp(elementIndex, 1, ctx.height, null, ctx);
|
||||
elementClassProp(elementIndex, 0, ctx.activateXYZClass, ctx);
|
||||
elementStylingApply(elementIndex, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
width: null|string = null;
|
||||
height: null|string = null;
|
||||
activateXYZClass: boolean = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <div DirWithInitialStyling class="abc" style="width:100px;
|
||||
* height:200px"></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', [
|
||||
'DirWithSingleStylingBindings', '', AttributeMarker.Classes, 'abc',
|
||||
AttributeMarker.Styles, 'width', '100px', 'height', '200px'
|
||||
]);
|
||||
}
|
||||
}, 1, 0, [DirWithSingleStylingBindings]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
const target = fixture.hostElement.querySelector('div') !;
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('200px');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeFalsy();
|
||||
|
||||
dirInstance !.width = '444px';
|
||||
dirInstance !.height = '999px';
|
||||
dirInstance !.activateXYZClass = true;
|
||||
fixture.update();
|
||||
|
||||
expect(target.style.getPropertyValue('width')).toEqual('444px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('999px');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeTruthy();
|
||||
|
||||
dirInstance !.width = null;
|
||||
dirInstance !.height = null;
|
||||
fixture.update();
|
||||
|
||||
expect(target.style.getPropertyValue('width')).toEqual('100px');
|
||||
expect(target.style.getPropertyValue('height')).toEqual('200px');
|
||||
expect(target.classList.contains('abc')).toBeTruthy();
|
||||
expect(target.classList.contains('def')).toBeTruthy();
|
||||
expect(target.classList.contains('xyz')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should properly prioritize style binding collision when they exist on multiple directives',
|
||||
() => {
|
||||
let dir1Instance: Dir1WithStyle;
|
||||
/**
|
||||
* Directive with host props:
|
||||
* [style.width]
|
||||
*/
|
||||
class Dir1WithStyle {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: Dir1WithStyle,
|
||||
selectors: [['', 'Dir1WithStyle', '']],
|
||||
factory: () => dir1Instance = new Dir1WithStyle(),
|
||||
hostBindings: function(rf: RenderFlags, ctx: Dir1WithStyle, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStyling(null, ['width'], null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(elementIndex, 0, ctx.width, null, ctx);
|
||||
elementStylingApply(elementIndex, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
width: null|string = null;
|
||||
}
|
||||
|
||||
let dir2Instance: Dir2WithStyle;
|
||||
/**
|
||||
* Directive with host props:
|
||||
* [style.width]
|
||||
* style="width:111px"
|
||||
*/
|
||||
class Dir2WithStyle {
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: Dir2WithStyle,
|
||||
selectors: [['', 'Dir2WithStyle', '']],
|
||||
factory: () => dir2Instance = new Dir2WithStyle(),
|
||||
hostBindings: function(rf: RenderFlags, ctx: Dir2WithStyle, elementIndex: number) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementHostAttrs(ctx, [AttributeMarker.Styles, 'width', '111px']);
|
||||
elementStyling(null, ['width'], null, ctx);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(elementIndex, 0, ctx.width, null, ctx);
|
||||
elementStylingApply(elementIndex, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
width: null|string = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <div Dir1WithStyle Dir2WithStyle [style.width]></div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'div', ['Dir1WithStyle', '', 'Dir2WithStyle', '']);
|
||||
elementStyling(null, ['width']);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementStyleProp(0, 0, ctx.width);
|
||||
elementStylingApply(0);
|
||||
}
|
||||
}, 1, 0, [Dir1WithStyle, Dir2WithStyle]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
const target = fixture.hostElement.querySelector('div') !;
|
||||
expect(target.style.getPropertyValue('width')).toEqual('111px');
|
||||
|
||||
fixture.component.width = '999px';
|
||||
dir1Instance !.width = '222px';
|
||||
dir2Instance !.width = '333px';
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('999px');
|
||||
|
||||
fixture.component.width = null;
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('222px');
|
||||
|
||||
dir1Instance !.width = null;
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('333px');
|
||||
|
||||
dir2Instance !.width = null;
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('111px');
|
||||
|
||||
dir1Instance !.width = '666px';
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('666px');
|
||||
|
||||
fixture.component.width = '777px';
|
||||
fixture.update();
|
||||
expect(target.style.getPropertyValue('width')).toEqual('777px');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -2657,4 +2868,4 @@ class ProxyRenderer3Factory implements RendererFactory3 {
|
||||
this.lastCapturedType = rendererType;
|
||||
return domRendererFactory3.createRenderer(hostElement, rendererType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,7 @@ describe('elementProperty', () => {
|
||||
function Template(rf: RenderFlags, ctx: string) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'span');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'id', rf & RenderFlags.Create ? expensive(ctx) : NO_CHANGE);
|
||||
elementProperty(0, 'id', expensive(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef} from '@angular/core/src/change_detection/change_detector_ref';
|
||||
import {Provider} from '@angular/core/src/di/provider';
|
||||
import {ElementRef} from '@angular/core/src/linker/element_ref';
|
||||
import {TemplateRef} from '@angular/core/src/linker/template_ref';
|
||||
import {ViewContainerRef} from '@angular/core/src/linker/view_container_ref';
|
||||
@ -24,7 +25,7 @@ import {CreateComponentOptions} from '../../src/render3/component';
|
||||
import {getDirectivesAtNodeIndex, getLContext, isComponentInstance} from '../../src/render3/context_discovery';
|
||||
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
|
||||
import {NG_ELEMENT_ID} from '../../src/render3/fields';
|
||||
import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
|
||||
import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, ProvidersFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index';
|
||||
import {renderTemplate} from '../../src/render3/instructions';
|
||||
import {DirectiveDefList, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
|
||||
import {PlayerHandler} from '../../src/render3/interfaces/player';
|
||||
@ -295,7 +296,8 @@ export function toHtml<T>(componentOrElement: T | RElement, keepNgReflect = fals
|
||||
export function createComponent(
|
||||
name: string, template: ComponentTemplate<any>, consts: number = 0, vars: number = 0,
|
||||
directives: DirectiveTypesOrFactory = [], pipes: PipeTypesOrFactory = [],
|
||||
viewQuery: ComponentTemplate<any>| null = null): ComponentType<any> {
|
||||
viewQuery: ComponentTemplate<any>| null = null, providers: Provider[] = [],
|
||||
viewProviders: Provider[] = []): ComponentType<any> {
|
||||
return class Component {
|
||||
value: any;
|
||||
static ngComponentDef = defineComponent({
|
||||
@ -307,7 +309,9 @@ export function createComponent(
|
||||
template: template,
|
||||
viewQuery: viewQuery,
|
||||
directives: directives,
|
||||
pipes: pipes
|
||||
pipes: pipes,
|
||||
features: (providers.length > 0 || viewProviders.length > 0)?
|
||||
[ProvidersFeature(providers || [], viewProviders || [])]: []
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -37,10 +37,13 @@ describe('renderer factory lifecycle', () => {
|
||||
consts: 1,
|
||||
vars: 0,
|
||||
template: function(rf: RenderFlags, ctx: SomeComponent) {
|
||||
logs.push('component');
|
||||
if (rf & RenderFlags.Create) {
|
||||
logs.push('component create');
|
||||
text(0, 'foo');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
logs.push('component update');
|
||||
}
|
||||
},
|
||||
factory: () => new SomeComponent
|
||||
});
|
||||
@ -61,31 +64,38 @@ describe('renderer factory lifecycle', () => {
|
||||
}
|
||||
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
logs.push('function');
|
||||
if (rf & RenderFlags.Create) {
|
||||
logs.push('function create');
|
||||
text(0, 'bar');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
logs.push('function update');
|
||||
}
|
||||
}
|
||||
|
||||
const directives = [SomeComponent, SomeComponentWhichThrows];
|
||||
|
||||
function TemplateWithComponent(rf: RenderFlags, ctx: any) {
|
||||
logs.push('function_with_component');
|
||||
if (rf & RenderFlags.Create) {
|
||||
logs.push('function_with_component create');
|
||||
text(0, 'bar');
|
||||
element(1, 'some-component');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
logs.push('function_with_component update');
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => { logs = []; });
|
||||
|
||||
it('should work with a component', () => {
|
||||
const component = renderComponent(SomeComponent, {rendererFactory});
|
||||
expect(logs).toEqual(['create', 'create', 'begin', 'component', 'end']);
|
||||
expect(logs).toEqual(
|
||||
['create', 'create', 'begin', 'component create', 'component update', 'end']);
|
||||
|
||||
logs = [];
|
||||
tick(component);
|
||||
expect(logs).toEqual(['begin', 'component', 'end']);
|
||||
expect(logs).toEqual(['begin', 'component update', 'end']);
|
||||
});
|
||||
|
||||
it('should work with a component which throws', () => {
|
||||
@ -95,21 +105,23 @@ describe('renderer factory lifecycle', () => {
|
||||
|
||||
it('should work with a template', () => {
|
||||
renderToHtml(Template, {}, 1, 0, null, null, rendererFactory);
|
||||
expect(logs).toEqual(['create', 'begin', 'function', 'end']);
|
||||
expect(logs).toEqual(['create', 'begin', 'function create', 'function update', 'end']);
|
||||
|
||||
logs = [];
|
||||
renderToHtml(Template, {});
|
||||
expect(logs).toEqual(['begin', 'function', 'end']);
|
||||
expect(logs).toEqual(['begin', 'function update', 'end']);
|
||||
});
|
||||
|
||||
it('should work with a template which contains a component', () => {
|
||||
renderToHtml(TemplateWithComponent, {}, 2, 0, directives, null, rendererFactory);
|
||||
expect(logs).toEqual(
|
||||
['create', 'begin', 'function_with_component', 'create', 'component', 'end']);
|
||||
expect(logs).toEqual([
|
||||
'create', 'begin', 'function_with_component create', 'create', 'component create',
|
||||
'function_with_component update', 'component update', 'end'
|
||||
]);
|
||||
|
||||
logs = [];
|
||||
renderToHtml(TemplateWithComponent, {}, 2, 0, directives);
|
||||
expect(logs).toEqual(['begin', 'function_with_component', 'component', 'end']);
|
||||
expect(logs).toEqual(['begin', 'function_with_component update', 'component update', 'end']);
|
||||
});
|
||||
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user