├── .github └── workflows │ ├── alpha-publish.yml │ └── publish.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── assets └── package.json ├── commitlint.config.js ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── readme.md ├── readme.zh-Hans.md ├── script ├── build.ts ├── sync │ ├── copy.json │ ├── dir-copy.json │ ├── extra.json │ └── index.ts └── test.esbuild.ts ├── src ├── import │ ├── change_detection │ │ └── scheduling │ │ │ ├── zoneless_scheduling.ts │ │ │ └── zoneless_scheduling_impl.ts │ ├── core_reactivity_export_internal.ts │ ├── di.ts │ ├── di │ │ ├── contextual.ts │ │ ├── create_injector.ts │ │ ├── forward_ref.ts │ │ ├── index.ts │ │ ├── initializer_token.ts │ │ ├── inject_switch.ts │ │ ├── injectable.ts │ │ ├── injection_token.ts │ │ ├── injector.ts │ │ ├── injector_compatibility.ts │ │ ├── injector_marker.ts │ │ ├── injector_token.ts │ │ ├── interface │ │ │ ├── defs.ts │ │ │ ├── injector.ts │ │ │ └── provider.ts │ │ ├── internal_tokens.ts │ │ ├── metadata.ts │ │ ├── null_injector.ts │ │ ├── provider_collection.ts │ │ ├── provider_token.ts │ │ ├── r3_injector.ts │ │ └── scope.ts │ ├── error_handler.ts │ ├── errors.ts │ ├── index.ts │ ├── interface │ │ ├── lifecycle_hooks.ts │ │ └── type.ts │ ├── linker │ │ └── destroy_ref.ts │ ├── pending_tasks.ts │ ├── render3 │ │ ├── definition_factory.ts │ │ ├── errors_di.ts │ │ ├── fields.ts │ │ ├── instructions │ │ │ └── di.ts │ │ ├── reactivity │ │ │ ├── api.ts │ │ │ ├── asserts.ts │ │ │ ├── computed.ts │ │ │ ├── effect.ts │ │ │ ├── linked_signal.ts │ │ │ ├── microtask_effect.ts │ │ │ ├── patch.ts │ │ │ ├── root_effect_scheduler.ts │ │ │ ├── signal.ts │ │ │ └── untracked.ts │ │ └── util │ │ │ └── stringify_utils.ts │ ├── resource │ │ ├── api.ts │ │ ├── index.ts │ │ └── resource.ts │ └── util │ │ ├── callback_scheduler.ts │ │ ├── closure.ts │ │ ├── decorators.ts │ │ ├── empty.ts │ │ ├── noop.ts │ │ ├── property.ts │ │ └── stringify.ts └── primitives │ └── signals │ ├── README.md │ ├── index.ts │ └── src │ ├── computed.ts │ ├── equality.ts │ ├── errors.ts │ ├── graph.ts │ ├── linked_signal.ts │ ├── signal.ts │ ├── watch.ts │ └── weak_ref.ts ├── test ├── fixture │ ├── demo.ts │ ├── destory.ts │ ├── hello-without-provide-object.ts │ ├── hello.ts │ ├── inherit.ts │ ├── inject-class.ts │ ├── inject-function.ts │ ├── injectable.ts │ ├── injection-token.ts │ ├── main1.ts │ ├── multi-provider.ts │ ├── other-decorator.ts │ ├── parameters-decorator.ts │ ├── provider.ts │ ├── sub-class.ts │ └── sub1.ts └── import │ ├── destroy.spec.ts │ ├── error.spec.ts │ ├── hello.spec.ts │ ├── inherit.spec.ts │ ├── inject-class.spec.ts │ ├── inject-function.spec.ts │ ├── injectable.spec.ts │ ├── injection-token.spec.ts │ ├── main1.spec.ts │ ├── multi-provider.spec.ts │ ├── other-decorator.spec.ts │ ├── parameters-decorator.spec.ts │ ├── provider.spec.ts │ ├── reactivity │ ├── computed.spec.ts │ ├── effect.spec.ts │ ├── linked_signal.spec.ts │ ├── resource.spec.ts │ ├── signal.spec.ts │ └── untracked.spec.ts │ └── sub-class.spec.ts ├── tsconfig.base.json ├── tsconfig.import-test.json ├── tsconfig.import.json ├── tsconfig.json ├── tsconfig.spec.json ├── tsconfig.type.json └── typings.d.ts /.github/workflows/alpha-publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - alpha 7 | env: 8 | REPOSITORY_PATH: https://${{secrets.ACCESS_TOKEN}}@github.com/ 9 | # GITHUB_TOKEN: ${{secrets.ACCESS_TOKEN}} 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: init 16 | run: | 17 | git config --global user.name "${GITHUB_ACTOR}" 18 | git config --global user.email "${GITHUB_ACTOR}@gmail.com" 19 | - name: pull-code 20 | uses: actions/checkout@v2 21 | - name: install-node 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: 20.x 25 | - name: install-dependencies 26 | run: | 27 | npm i 28 | - name: test 29 | run: | 30 | npm test 31 | - name: build 32 | run: | 33 | npm run build 34 | - id: publish 35 | name: publish 36 | uses: JS-DevTools/npm-publish@v3.1 37 | with: 38 | token: ${{ secrets.NPM_PUBLISH_TOKEN }} 39 | package: ./dist/package.json 40 | tag: alpha 41 | - if: steps.publish.outputs.old-version != steps.publish.outputs.version 42 | run: | 43 | echo "[${{ steps.publish.outputs.type }}]Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}" 44 | git tag v${{steps.publish.outputs.version}} 45 | git push origin v${{steps.publish.outputs.version}} 46 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | env: 8 | REPOSITORY_PATH: https://${{secrets.ACCESS_TOKEN}}@github.com/ 9 | # GITHUB_TOKEN: ${{secrets.ACCESS_TOKEN}} 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: init 16 | run: | 17 | git config --global user.name "${GITHUB_ACTOR}" 18 | git config --global user.email "${GITHUB_ACTOR}@gmail.com" 19 | - name: pull-code 20 | uses: actions/checkout@v2 21 | - name: install-node 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: 20.x 25 | - name: install-dependencies 26 | run: | 27 | npm i 28 | - name: test 29 | run: | 30 | npm test 31 | - name: build 32 | run: | 33 | npm run build 34 | - id: publish 35 | name: publish 36 | uses: JS-DevTools/npm-publish@v3.1 37 | with: 38 | token: ${{ secrets.NPM_PUBLISH_TOKEN }} 39 | package: ./dist/package.json 40 | - if: steps.publish.outputs.old-version != steps.publish.outputs.version 41 | run: | 42 | echo "[${{ steps.publish.outputs.type }}]Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}" 43 | git tag v${{steps.publish.outputs.version}} 44 | git push origin v${{steps.publish.outputs.version}} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | yarn-error.log 40 | testem.log 41 | /typings 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | 47 | 48 | /src/**/*.js 49 | /test/**/*.js 50 | !/test/util/jest-test-transformer-loader.js 51 | coverage-import 52 | 53 | .temp-git 54 | 55 | /test-dist -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { "singleQuote": true } 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "测试error", 9 | "type": "node", 10 | "request": "launch", 11 | "cwd": "${workspaceRoot}", 12 | "runtimeExecutable": "npm", 13 | "runtimeArgs": ["run-script", "test", "--", "-t", "error"], 14 | "port": 5858 15 | }, 16 | { 17 | "name": "测试default", 18 | "type": "node", 19 | "request": "launch", 20 | "cwd": "${workspaceRoot}", 21 | "runtimeExecutable": "npm", 22 | "runtimeArgs": ["run-script", "test", "--", "-t", "default"], 23 | "port": 5858 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-injector", 3 | "version": "6.0.3", 4 | "description": "Angular 依赖注入独立版本;Angular dependency injection standalone version", 5 | "keywords": [ 6 | "angular", 7 | "angular 19.2.0", 8 | "injector", 9 | "typescript", 10 | "injectable", 11 | "static-inject", 12 | "dependency injection", 13 | "injection-js", 14 | "dependency inversion", 15 | "di", 16 | "inversion of control container", 17 | "ioc", 18 | "javascript", 19 | "node", 20 | "signal", 21 | "reactivity", 22 | "effect", 23 | "computed", 24 | "resource", 25 | "linkedSignal" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/wszgrcy/static-injector.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/wszgrcy/static-injector/issues" 33 | }, 34 | "homepage": "https://github.com/wszgrcy/static-injector#readme", 35 | "main": "index.js", 36 | "exports": { 37 | ".": { 38 | "import": "./index.mjs", 39 | "require": "./index.js", 40 | "default": "./index.mjs", 41 | "types": "./import/index.d.ts" 42 | } 43 | }, 44 | "private": false, 45 | "scripts": {}, 46 | "author": "wszgrcy", 47 | "license": "MIT", 48 | "peerDependencies": { 49 | "rxjs": "^7.8.0" 50 | }, 51 | "devDependencies": {}, 52 | "sideEffects": false 53 | } -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | import unusedImports from 'eslint-plugin-unused-imports'; 5 | 6 | 7 | /** @type {import('eslint').Linter.Config[]} */ 8 | export default [ 9 | { 10 | files: ["**/*.{js,mjs,cjs,ts}"], plugins: { 11 | 'unused-imports': unusedImports, 12 | }, 13 | rules: { 14 | 'no-unused-vars': 'off', 15 | 'unused-imports/no-unused-imports': 'warn', 16 | 'unused-imports/no-unused-vars': [ 17 | 'warn', 18 | { 19 | vars: 'all', 20 | varsIgnorePattern: '^_', 21 | args: 'after-used', 22 | argsIgnorePattern: '^_', 23 | }, 24 | ], 25 | }, 26 | }, 27 | { languageOptions: { globals: globals.browser } }, 28 | pluginJs.configs.recommended, 29 | ...tseslint.configs.recommended, 30 | { 31 | rules: { 32 | 'arrow-body-style': ["warn", "as-needed"], 33 | '@typescript-eslint/no-explicit-any': 'off', 34 | 'no-unused-private-class-members': 'off', 35 | '@typescript-eslint/no-unused-vars': 'off', 36 | 'no-useless-escape': 'off', 37 | '@typescript-eslint/no-var-requires': 'off', 38 | 'no-useless-catch': 'off', 39 | 'no-constant-condition': 'off', 40 | 'no-empty': 'off', 41 | 'no-empty-pattern': 'off', 42 | 'no-unsafe-finally': 'off', 43 | 'no-async-promise-executor': 'off', 44 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', 45 | '@typescript-eslint/ban-ts-comment': 'off', 46 | '@typescript-eslint/no-unused-vars': 'off', 47 | '@typescript-eslint/no-require-imports': 'off', 48 | '@typescript-eslint/no-unused-expressions': 'off', 49 | 'eqeqeq': "warn", 50 | 'no-useless-computed-key': 'warn', 51 | 'prefer-const':"warn", 52 | "@typescript-eslint/no-unsafe-function-type":"warn", 53 | "@typescript-eslint/no-empty-object-type":"warn", 54 | "no-prototype-builtins":"warn", 55 | }, 56 | }, 57 | ]; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-injector", 3 | "version": "0.0.0", 4 | "description": "Angular 依赖注入独立版本;Angular dependency injection standalone version", 5 | "private": true, 6 | "scripts": { 7 | "sync": "code-recycle ./script/sync/index.ts --cwd ./src && eslint --fix ./src && prettier ./src --write", 8 | "test": "rimraf -g ./test-dist/**/*.mjs && tsx ./script/test.esbuild.ts && mocha \"./test-dist/**/*.spec.mjs\" --enable-source-maps --inspect", 9 | "build": "rimraf dist && npm run build:import", 10 | "build:import": "tsx ./script/build && tsc -p tsconfig.type.json && cpx ./assets/package.json ./dist && cpx -v ./readme.md ./dist" 11 | }, 12 | "author": "wszgrcy", 13 | "license": "MIT", 14 | "peerDependencies": { 15 | "typescript": ">=5.4.2" 16 | }, 17 | "devDependencies": { 18 | "@code-recycle/cli": "1.3.10", 19 | "@commitlint/cli": "^12.1.4", 20 | "@commitlint/config-conventional": "^12.1.4", 21 | "@types/chai": "^5.0.1", 22 | "@types/mocha": "^10.0.10", 23 | "@types/node": "^20.11.30", 24 | "chai": "^5.2.0", 25 | "cpx": "^1.5.0", 26 | "esbuild": "^0.25.0", 27 | "eslint": "^9.21.0", 28 | "eslint-plugin-unused-imports": "^4.1.4", 29 | "fast-glob": "^3.3.3", 30 | "globals": "^16.0.0", 31 | "husky": "^7.0.1", 32 | "mocha": "^11.1.0", 33 | "prettier": "^3.5.2", 34 | "pretty-quick": "^4.0.0", 35 | "rimraf": "^6.0.1", 36 | "rxjs": "^7.8.2", 37 | "tsx": "^4.19.3", 38 | "typescript": "^5.7.0", 39 | "typescript-eslint": "^8.25.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | | [中文](https://github.com/wszgrcy/static-injector/blob/main/readme.zh-Hans.md) | [English](./readme.md) | 2 | | ------------------------------------------------------------------------------ | ---------------------- | 3 | 4 | # Introduction 5 | 6 | - Angular dependency injection standalone version 7 | - The usage method is completely consistent with Angular's dependency injection 8 | - No transformer required 9 | - 0 dependencies 10 | - Remove Decorator 11 | > `@Injectable()`=>`static injectOptions={}` > `@Inject() xx`=>`xx=inject()` > `@Optional()`=>`xx=inject(token,{optional:true})` 12 | - `JS`/`TS` Support 13 | 14 | # Source 15 | 16 | - Angular 19.2.0 17 | 18 | # Usage 19 | 20 | - Create a first level dependency injector with `createRootInjector` 21 | 22 | ```ts 23 | import { 24 | Injector, 25 | inject, 26 | // root 27 | createRootInjector, 28 | // child 29 | createInjector, 30 | } from 'static-injector'; 31 | 32 | class Main { 33 | child = inject(Child); 34 | } 35 | class Child { 36 | output() { 37 | return 'hello world'; 38 | } 39 | } 40 | let injector = createRootInjector({ providers: [Main, Child] }); 41 | const instance = injector.get(Main); 42 | console.log(instance.child.output()); 43 | ``` 44 | 45 | # No Decorator 46 | 47 | - The original use of `@Injectable()` to pass parameters has been changed to `static injectOptions={}`. If there are no parameters, there is no need to set them 48 | - Originally, `@Optional`, `@SkipSelf`, `@Self`, please use the second pass parameter of `inject` instead 49 | 50 | # reactivity 51 | 52 | - support `signal`,`effect`,`resource`,`linkedSignal`,`computed` 53 | 54 | # Test 55 | 56 | - Partially conducted unit testing to ensure that most functions are functioning properly 57 | - Because most of the code itself is extracted from Angular, stability is definitely guaranteed 58 | 59 | # Sync 60 | 61 | - Currently, the synchronization logic has been refactored and modified using `@code-recycle/cli` to ensure consistency with the official version of `angular` 62 | 63 | # Examples 64 | 65 | - [https://github.com/wszgrcy/static-injector/tree/main/test/import](https://github.com/wszgrcy/static-injector/tree/main/test/import) 66 | -------------------------------------------------------------------------------- /readme.zh-Hans.md: -------------------------------------------------------------------------------- 1 | | [中文](https://github.com/wszgrcy/static-injector/blob/main/readme.zh-Hans.md) | [English](./readme.md) | 2 | | ------------------------------------------------------------------------------ | ---------------------- | 3 | 4 | # 简介 5 | 6 | - Angular 依赖注入的独立版本 7 | - 使用方法与 Angular 的依赖注入完全一致 8 | - 不需要任何转换器,引入即可使用 9 | - 0依赖 10 | - 移除装饰器 11 | > `@Injectable()`=>`static injectOptions={}` > `@Inject() xx`=>`xx=inject()` > `@Optional()`=>`xx=inject(token,{optional:true})` 12 | - `JS`/`TS`支持 13 | 14 | # 来源 15 | 16 | - Angular 19.2.0 17 | 18 | # 使用方法 19 | 20 | - 以`createRootInjector`创建第一级依赖注入器 21 | 22 | ```ts 23 | import { 24 | Injector, 25 | inject, 26 | // 根级 27 | createRootInjector, 28 | // 非根级 29 | createInjector, 30 | } from 'static-injector'; 31 | 32 | class Main { 33 | child = inject(Child); 34 | } 35 | class Child { 36 | output() { 37 | return 'hello world'; 38 | } 39 | } 40 | let injector = createRootInjector({ providers: [Main, Child] }); 41 | const instance = injector.get(Main); 42 | console.log(instance.child.output()); 43 | ``` 44 | 45 | # 无装饰器 46 | 47 | - 原来使用`@Injectable()`传参改为`static injectOptions={}`.如果没有参数即不需要设置 48 | - 原来`@Optional`,`@SkipSelf`,`@Self`,请使用`inject`的第二个传参代替 49 | 50 | # 响应式 51 | - 支持`signal`,`effect`,`resource`,`linkedSignal`,`computed` 52 | # 测试 53 | 54 | - 做了一部分的单元测试.保证大部分功能正常使用 55 | - 因为大部分代码本身就是从 Angular 中提取的,所以稳定性肯定也是有保证 56 | 57 | # 同步 58 | 59 | - 目前重构了同步逻辑,使用`@code-recycle/cli`进行修改,保证与`angular`官方版本一致 60 | 61 | # 实例 62 | 63 | - [https://github.com/wszgrcy/static-injector/tree/main/test/import](https://github.com/wszgrcy/static-injector/tree/main/test/import) 64 | -------------------------------------------------------------------------------- /script/build.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild'; 2 | import * as path from 'path'; 3 | import * as glob from 'fast-glob'; 4 | async function bundleImport() { 5 | let options: esbuild.BuildOptions = { 6 | platform: 'node', 7 | sourcemap: 'linked', 8 | bundle: true, 9 | entryPoints: [{ in: './src/import/index.ts', out: './index' }], 10 | splitting: false, 11 | outdir: path.join(process.cwd(), './test-dist'), 12 | outExtension: { 13 | '.js': '.mjs', 14 | }, 15 | format: 'esm', 16 | minify: false, 17 | tsconfig: 'tsconfig.import.json', 18 | charset: 'utf8', 19 | external: ['rxjs', 'mocha', 'chai'], 20 | define: { 21 | ngDevMode: 'false', 22 | Zone: 'undefined', 23 | }, 24 | }; 25 | await esbuild.build({ ...options, outdir: './dist' }); 26 | await esbuild.build({ 27 | ...options, 28 | outdir: './dist', 29 | format: 'cjs', 30 | outExtension: {}, 31 | }); 32 | } 33 | 34 | bundleImport(); 35 | -------------------------------------------------------------------------------- /script/sync/copy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "source": "core/src", 4 | "target": "./import", 5 | "fileList": [ 6 | "/di.ts", 7 | "/di/index.ts", 8 | "/di/create_injector.ts", 9 | "/di/forward_ref.ts", 10 | "/di/initializer_token.ts", 11 | "/di/injectable.ts", 12 | "/di/injection_token.ts", 13 | "/di/injector.ts", 14 | "/di/injector_compatibility.ts", 15 | "/di/injector_marker.ts", 16 | "/di/injector_token.ts", 17 | "/di/inject_switch.ts", 18 | "/di/internal_tokens.ts", 19 | "/di/metadata.ts", 20 | "/di/null_injector.ts", 21 | "/di/provider_collection.ts", 22 | "/di/provider_token.ts", 23 | "/di/r3_injector.ts", 24 | "/di/scope.ts", 25 | "/di/interface/defs.ts", 26 | "/di/interface/injector.ts", 27 | "/di/interface/provider.ts", 28 | "/interface/lifecycle_hooks.ts", 29 | "/interface/type.ts", 30 | "/render3/definition_factory.ts", 31 | "/render3/errors_di.ts", 32 | "/render3/fields.ts", 33 | "/render3/instructions/di.ts", 34 | "/render3/util/stringify_utils.ts", 35 | "/util/closure.ts", 36 | "/util/decorators.ts", 37 | "/util/empty.ts", 38 | "/util/property.ts", 39 | "/util/stringify.ts", 40 | "/errors.ts", 41 | "/pending_tasks.ts", 42 | "/error_handler.ts", 43 | "/di/contextual.ts", 44 | "/linker/destroy_ref.ts", 45 | "/util/noop.ts", 46 | "/change_detection/scheduling/zoneless_scheduling.ts", 47 | "/core_reactivity_export_internal.ts", 48 | "/util/callback_scheduler.ts" 49 | ] 50 | } 51 | ] -------------------------------------------------------------------------------- /script/sync/dir-copy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "source": "core/src", 4 | "target": "./import", 5 | "fileList": [ 6 | "render3/reactivity", 7 | "resource" 8 | ] 9 | }, 10 | { 11 | "source": "core", 12 | "target": "", 13 | "fileList": [ 14 | "primitives/signals" 15 | ] 16 | } 17 | ] -------------------------------------------------------------------------------- /script/sync/index.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | NodeQueryOption, 3 | ScriptFunction, 4 | FileQueryLayer, 5 | } from '@code-recycle/cli'; 6 | 7 | const typeMap = { 8 | class: 'ClassDeclaration', 9 | enum: 'EnumDeclaration', 10 | interface: 'InterfaceDeclaration', 11 | function: 'FunctionDeclaration', 12 | type: 'TypeAliasDeclaration', 13 | 'interface-method': 'MethodSignature', 14 | 'interface-property': 'PropertySignature', 15 | 'class-method': 'MethodDeclaration', 16 | 'enum-member': 'EnumMember', 17 | variable: 'VariableDeclaration', 18 | }; 19 | function importDeclarationByNamed(list: string[]) { 20 | let listStr = list.map((item) => `[value=${item}]`).join(','); 21 | // return `ImportSpecifier:has(>::name:is({{input}}))::name:is(${listStr})) `[value=${item}]`).join(','); 26 | 27 | // return `NamespaceImport:has(>::name:is({{input}}))::name:is(${listStr})) `[value=${item}]`).join(','); 32 | // return `ImportSpecifier:has(>::name:is({{input}})):use(*,+CommaToken)`; 33 | return `ImportSpecifier:has(>::name:is(${listStr})):use(*,+CommaToken)`; 34 | } 35 | export function variableStatement(nameList: string[]) { 36 | let listStr = nameList.map((item) => `[value=${item}]`).join(','); 37 | 38 | // `VariableDeclaration:has(>::name:is({{input}}))::name:is(${listStr})) { 43 | let path = util.path; 44 | 45 | let data = await rule.os.gitClone( 46 | 'https://github.com/angular/angular.git', 47 | [ 48 | '/packages/core/src', 49 | '/packages/core/primitives', 50 | '!**/*.bazel', 51 | '!**/*spec.ts', 52 | '!**/*.js', 53 | '!/packages/compiler-cli/test', 54 | ], 55 | 'packages', 56 | 'branch', 57 | '19.2.0', 58 | ); 59 | let copyData = require('./copy.json') as { 60 | source: string; 61 | target: string; 62 | fileList: string[]; 63 | }[]; 64 | 65 | for (const item of copyData) { 66 | let nDir = path.normalize(item.source); 67 | for (const filePath of item.fileList) { 68 | let file = data[path.join(nDir, filePath)]; 69 | if (!file) { 70 | throw new Error(`${file} 不存在`); 71 | } 72 | await new Promise((res) => 73 | host 74 | .write(path.join(path.normalize(item.target), filePath), file) 75 | .subscribe({ 76 | complete: () => res(undefined), 77 | }), 78 | ); 79 | } 80 | } 81 | let copyData2 = require('./dir-copy.json') as { 82 | source: string; 83 | target: string; 84 | fileList: string[]; 85 | }[]; 86 | 87 | let fileList = Object.keys(data); 88 | for (const item of copyData2) { 89 | for (const fileItem of item.fileList) { 90 | let fullGlobPath = path.join(path.normalize(item.source), fileItem); 91 | 92 | for (const fileName of fileList) { 93 | let result = fileName.startsWith(fullGlobPath); 94 | if (!result) { 95 | continue; 96 | } 97 | let relPath = fileName.slice(item.source.length); 98 | let writeFile = item.target 99 | ? path.join(path.normalize(item.target), relPath) 100 | : path.normalize('.' + relPath); 101 | if ( 102 | writeFile === 'import/render3/reactivity/after_render_effect.ts' || 103 | writeFile === 'import/render3/reactivity/view_effect_runner.ts' 104 | ) { 105 | continue; 106 | } 107 | await new Promise((res) => 108 | host 109 | .write( 110 | item.target 111 | ? path.join(path.normalize(item.target), relPath) 112 | : path.normalize('.' + relPath), 113 | data[fileName], 114 | ) 115 | .subscribe({ 116 | complete: () => res(undefined), 117 | }), 118 | ); 119 | } 120 | } 121 | } 122 | 123 | function createOption(item: { 124 | type: string; 125 | change: string; 126 | values: { 127 | excludes?: string[]; 128 | selector?: string; 129 | namespaces?: string[]; 130 | all?: boolean; 131 | namedImports?: string[]; 132 | global?: boolean; 133 | content?: string; 134 | includes?: string[]; 135 | removeComment?: boolean; 136 | replaceSelector?: string; 137 | }; 138 | }): NodeQueryOption { 139 | const range = async (context) => { 140 | return [ 141 | await rule.common.getData(context, 'node.node.extra.pos'), 142 | await rule.common.getData(context, 'node.node.extra.end'), 143 | ] as [number, number]; 144 | }; 145 | if (!item.change || item.change === 'remove') { 146 | if (item.type === 'import') { 147 | if (item.values.all) { 148 | return { 149 | delete: true, 150 | query: `ImportDeclaration`, 151 | multi: true, 152 | }; 153 | } else if (item.values.excludes) { 154 | return { 155 | delete: true, 156 | query: importDeclarationByNamed(item.values.excludes), 157 | multi: true, 158 | }; 159 | } else if (item.values.namespaces) { 160 | return { 161 | delete: true, 162 | query: importDeclarationByNameSpace(item.values.namespaces), 163 | multi: true, 164 | }; 165 | } else if (item.values.namedImports) { 166 | return { 167 | delete: true, 168 | query: namedImportsSelector(item.values.namedImports), 169 | multi: true, 170 | }; 171 | } 172 | } else if (item.type === 'custom') { 173 | if (item.values.selector) { 174 | return { 175 | delete: true, 176 | query: item.values.selector, 177 | multi: true, 178 | range: async (context) => { 179 | if (!item.values.removeComment) { 180 | return context.node!.node!.range; 181 | } else { 182 | return await rule.common.getData( 183 | context, 184 | 'node.node.extra.rangeWithComment', 185 | ); 186 | } 187 | }, 188 | }; 189 | } 190 | } else if (item.type === 'variable') { 191 | if (item.values.excludes) { 192 | return { 193 | delete: true, 194 | query: variableStatement(item.values.excludes), 195 | multi: true, 196 | range: range, 197 | }; 198 | } 199 | } else { 200 | let query = `${item.values.selector! || ''} ${typeMap[item.type]}`; 201 | let add = ''; 202 | if (item.values.includes) { 203 | if (item.values.includes.length) { 204 | add = `:has(>::name:not(:is(${item.values.includes 205 | .map((item) => '[value=' + item + ']') 206 | .join(',')})))`; 207 | } else { 208 | add = `:has(>::name)`; 209 | } 210 | } else if (item.values.excludes && item.values.excludes.length) { 211 | add = `:has(>::name:is(${item.values.excludes 212 | .map((item) => '[value=' + item + ']') 213 | .join(',')}))`; 214 | } 215 | return { 216 | delete: true, 217 | query: query + add, 218 | multi: true, 219 | range: range, 220 | }; 221 | } 222 | } else if (item.change === 'change') { 223 | if (item.type === 'custom') { 224 | if (item.values.content) { 225 | return { 226 | query: item.values.selector, 227 | replace: item.values.content, 228 | multi: true, 229 | range: range, 230 | }; 231 | } else if (item.values.replaceSelector) { 232 | return { 233 | query: item.values.selector, 234 | multi: true, 235 | // range: range, 236 | children: [{ query: item.values.replaceSelector }], 237 | callback(context, index) { 238 | return { 239 | range: context.node!.node!.range, 240 | value: context.getNode('0').node!.value, 241 | }; 242 | }, 243 | }; 244 | } 245 | } 246 | } else { 247 | throw new Error(''); 248 | } 249 | throw new Error(''); 250 | } 251 | 252 | let changeData = require('./extra.json') as { 253 | glob: boolean; 254 | fileName: string; 255 | rules: any[]; 256 | }[]; 257 | let list = await util.changeList( 258 | changeData.map((item) => { 259 | return { 260 | glob: !!item.glob, 261 | path: item.fileName, 262 | list: item.rules.map((ruleItem) => { 263 | let option = createOption(ruleItem); 264 | if (item.glob) { 265 | option.optional = true; 266 | } 267 | return option; 268 | }), 269 | }; 270 | }), 271 | ); 272 | await util.updateChangeList(list); 273 | }; 274 | export default fn; 275 | -------------------------------------------------------------------------------- /script/test.esbuild.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild'; 2 | import * as path from 'path'; 3 | import * as glob from 'fast-glob'; 4 | 5 | async function main() { 6 | let options: esbuild.BuildOptions = { 7 | platform: 'node', 8 | sourcemap: 'linked', 9 | bundle: true, 10 | entryPoints: [ 11 | ...glob.sync('./test/**/*.spec.ts', {}).map((item) => { 12 | return { in: item, out: path.join('', item.slice(0, -3)) }; 13 | }), 14 | ], 15 | splitting: true, 16 | outdir: path.join(process.cwd(), './test-dist'), 17 | outExtension: { 18 | '.js': '.mjs', 19 | }, 20 | format: 'esm', 21 | // minify: true, 22 | tsconfig: 'tsconfig.spec.json', 23 | charset: 'utf8', 24 | external: ['rxjs', 'mocha', 'chai'], 25 | define: { 26 | ngDevMode: 'false', 27 | Zone: 'undefined', 28 | }, 29 | }; 30 | await esbuild.build(options); 31 | } 32 | main(); 33 | -------------------------------------------------------------------------------- /src/import/change_detection/scheduling/zoneless_scheduling.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { InjectionToken } from '../../di/injection_token'; 10 | 11 | export const enum NotificationSource { 12 | // Change detection needs to run in order to synchronize application state 13 | // with the DOM when the following notifications are received: 14 | // This operation indicates that a subtree needs to be traversed during change detection. 15 | MarkAncestorsForTraversal, 16 | // A component/directive gets a new input. 17 | SetInput, 18 | // Defer block state updates need change detection to fully render the state. 19 | DeferBlockStateUpdate, 20 | // Debugging tools updated state and have requested change detection. 21 | DebugApplyChanges, 22 | // ChangeDetectorRef.markForCheck indicates the component is dirty/needs to refresh. 23 | MarkForCheck, 24 | 25 | // Bound listener callbacks execute and can update state without causing other notifications from 26 | // above. 27 | Listener, 28 | 29 | // Custom elements do sometimes require checking directly. 30 | CustomElement, 31 | 32 | // The following notifications do not require views to be refreshed 33 | // but we should execute render hooks: 34 | // Render hooks are guaranteed to execute with the schedulers timing. 35 | RenderHook, 36 | // Views might be created outside and manipulated in ways that 37 | // we cannot be aware of. When a view is attached, Angular now "knows" 38 | // about it and we now know that DOM might have changed (and we should 39 | // run render hooks). If the attached view is dirty, the `MarkAncestorsForTraversal` 40 | // notification should also be received. 41 | ViewAttached, 42 | // When DOM removal happens, render hooks may be interested in the new 43 | // DOM state but we do not need to refresh any views unless. If change 44 | // detection is required after DOM removal, another notification should 45 | // be received (i.e. `markForCheck`). 46 | ViewDetachedFromDOM, 47 | // Applying animations might result in new DOM state and should rerun render hooks 48 | AsyncAnimationsLoaded, 49 | // The scheduler is notified when a pending task is removed via the public API. 50 | // This allows us to make stability async, delayed until the next application tick. 51 | PendingTaskRemoved, 52 | // An `effect()` outside of the view tree became dirty and might need to run. 53 | RootEffect, 54 | // An `effect()` within the view tree became dirty. 55 | ViewEffect, 56 | } 57 | 58 | /** 59 | * Injectable that is notified when an `LView` is made aware of changes to application state. 60 | */ 61 | export abstract class ChangeDetectionScheduler { 62 | abstract notify(source: NotificationSource): void; 63 | abstract runningTick: boolean; 64 | } 65 | 66 | /** Token used to indicate if zoneless was enabled via provideZonelessChangeDetection(). */ 67 | export const ZONELESS_ENABLED = new InjectionToken( 68 | typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless enabled' : '', 69 | { providedIn: 'root', factory: () => false }, 70 | ); 71 | 72 | /** Token used to indicate `provideExperimentalZonelessChangeDetection` was used. */ 73 | export const PROVIDED_ZONELESS = new InjectionToken( 74 | typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless provided' : '', 75 | { providedIn: 'root', factory: () => false }, 76 | ); 77 | 78 | export const ZONELESS_SCHEDULER_DISABLED = new InjectionToken( 79 | typeof ngDevMode === 'undefined' || ngDevMode ? 'scheduler disabled' : '', 80 | ); 81 | 82 | // TODO(atscott): Remove in v19. Scheduler should be done with runOutsideAngular. 83 | export const SCHEDULE_IN_ROOT_ZONE = new InjectionToken( 84 | typeof ngDevMode === 'undefined' || ngDevMode 85 | ? 'run changes outside zone in root' 86 | : '', 87 | ); 88 | -------------------------------------------------------------------------------- /src/import/change_detection/scheduling/zoneless_scheduling_impl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { inject } from '../../di/injector_compatibility'; 10 | import { scheduleCallbackWithRafRace } from '../../util/callback_scheduler'; 11 | 12 | import { 13 | ChangeDetectionScheduler, 14 | NotificationSource, 15 | } from './zoneless_scheduling'; 16 | import { EffectScheduler } from 'src/import/render3/reactivity/root_effect_scheduler'; 17 | 18 | export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler { 19 | runningTick = false; 20 | #rootEffectScheduler = inject(EffectScheduler); 21 | private cancelScheduledCallback: null | (() => void) = null; 22 | 23 | notify(source: NotificationSource): void { 24 | this.cancelScheduledCallback = scheduleCallbackWithRafRace(() => { 25 | this.#rootEffectScheduler.flush(); 26 | }); 27 | } 28 | private cleanup() { 29 | this.runningTick = false; 30 | this.cancelScheduledCallback?.(); 31 | this.cancelScheduledCallback = null; 32 | } 33 | ngOnDestroy() { 34 | this.cleanup(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/import/core_reactivity_export_internal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | export { SIGNAL as ɵSIGNAL } from '@angular/core/primitives/signals'; 10 | 11 | export { isSignal, Signal, ValueEqualityFn } from './render3/reactivity/api'; 12 | export { computed, CreateComputedOptions } from './render3/reactivity/computed'; 13 | export { 14 | CreateSignalOptions, 15 | signal, 16 | WritableSignal, 17 | ɵunwrapWritableSignal, 18 | } from './render3/reactivity/signal'; 19 | export { linkedSignal } from './render3/reactivity/linked_signal'; 20 | export { untracked } from './render3/reactivity/untracked'; 21 | export { 22 | CreateEffectOptions, 23 | effect, 24 | EffectRef, 25 | EffectCleanupFn, 26 | EffectCleanupRegisterFn, 27 | } from './render3/reactivity/effect'; 28 | export { 29 | MicrotaskEffectScheduler as ɵMicrotaskEffectScheduler, 30 | microtaskEffect as ɵmicrotaskEffect, 31 | } from './render3/reactivity/microtask_effect'; 32 | export { EffectScheduler as ɵEffectScheduler } from './render3/reactivity/root_effect_scheduler'; 33 | 34 | export { assertNotInReactiveContext } from './render3/reactivity/asserts'; 35 | -------------------------------------------------------------------------------- /src/import/di.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * This file should not be necessary because node resolution should just default to `./di/index`! 11 | * 12 | * However it does not seem to work and it breaks: 13 | * - //packages/animations/browser/test:test_web_chromium-local 14 | * - //packages/compiler-cli/test:extract_i18n 15 | * - //packages/compiler-cli/test:ngc 16 | * - //packages/compiler-cli/test:perform_watch 17 | * - //packages/compiler-cli/test/diagnostics:check_types 18 | * - //packages/compiler-cli/test/transformers:test 19 | * - //packages/compiler/test:test 20 | * - //tools/public_api_guard:core_api 21 | * 22 | * Remove this file once the above is solved or wait until `ngc` is deleted and then it should be 23 | * safe to delete this file. 24 | */ 25 | 26 | export * from './di/index'; 27 | -------------------------------------------------------------------------------- /src/import/di/contextual.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { RuntimeError, RuntimeErrorCode } from '../errors'; 10 | 11 | import { 12 | getInjectImplementation, 13 | setInjectImplementation, 14 | } from './inject_switch'; 15 | import type { Injector } from './injector'; 16 | import { 17 | getCurrentInjector, 18 | setCurrentInjector, 19 | } from './injector_compatibility'; 20 | import { assertNotDestroyed, R3Injector } from './r3_injector'; 21 | 22 | /** 23 | * Runs the given function in the [context](guide/di/dependency-injection-context) of the given 24 | * `Injector`. 25 | * 26 | * Within the function's stack frame, [`inject`](api/core/inject) can be used to inject dependencies 27 | * from the given `Injector`. Note that `inject` is only usable synchronously, and cannot be used in 28 | * any asynchronous callbacks or after any `await` points. 29 | * 30 | * @param injector the injector which will satisfy calls to [`inject`](api/core/inject) while `fn` 31 | * is executing 32 | * @param fn the closure to be run in the context of `injector` 33 | * @returns the return value of the function, if any 34 | * @publicApi 35 | */ 36 | export function runInInjectionContext( 37 | injector: Injector, 38 | fn: () => ReturnT, 39 | ): ReturnT { 40 | if (injector instanceof R3Injector) { 41 | assertNotDestroyed(injector); 42 | } 43 | 44 | if (false) { 45 | } 46 | const prevInjector = setCurrentInjector(injector); 47 | const previousInjectImplementation = setInjectImplementation(undefined); 48 | try { 49 | return fn(); 50 | } finally { 51 | setCurrentInjector(prevInjector); 52 | 53 | setInjectImplementation(previousInjectImplementation); 54 | } 55 | } 56 | 57 | /** 58 | * Whether the current stack frame is inside an injection context. 59 | */ 60 | export function isInInjectionContext(): boolean { 61 | return ( 62 | getInjectImplementation() !== undefined || getCurrentInjector() != null 63 | ); 64 | } 65 | /** 66 | * Asserts that the current stack frame is within an [injection 67 | * context](guide/di/dependency-injection-context) and has access to `inject`. 68 | * 69 | * @param debugFn a reference to the function making the assertion (used for the error message). 70 | * 71 | * @publicApi 72 | */ 73 | export function assertInInjectionContext(debugFn: Function): void { 74 | // Taking a `Function` instead of a string name here prevents the unminified name of the function 75 | // from being retained in the bundle regardless of minification. 76 | if (!isInInjectionContext()) { 77 | throw new RuntimeError(RuntimeErrorCode.MISSING_INJECTION_CONTEXT, null); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/import/di/create_injector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { EMPTY_ARRAY } from '../util/empty'; 10 | import { stringify } from '../util/stringify'; 11 | 12 | import type { Injector } from './injector'; 13 | import type { Provider, StaticProvider } from './interface/provider'; 14 | import { importProvidersFrom } from './provider_collection'; 15 | import { getNullInjector, R3Injector } from './r3_injector'; 16 | import { InjectorScope } from './scope'; 17 | 18 | /** 19 | * Create a new `Injector` which is configured using a `defType` of `InjectorType`s. 20 | */ 21 | export function createInjector( 22 | defType: /* InjectorType */ any, 23 | parent: Injector | null = null, 24 | additionalProviders: Array | null = null, 25 | name?: string, 26 | ): Injector { 27 | const injector = createInjectorWithoutInjectorInstances( 28 | defType, 29 | parent, 30 | additionalProviders, 31 | name, 32 | ); 33 | injector.resolveInjectorInitializers(); 34 | return injector; 35 | } 36 | 37 | /** 38 | * Creates a new injector without eagerly resolving its injector types. Can be used in places 39 | * where resolving the injector types immediately can lead to an infinite loop. The injector types 40 | * should be resolved at a later point by calling `_resolveInjectorDefTypes`. 41 | */ 42 | export function createInjectorWithoutInjectorInstances( 43 | defType: /* InjectorType */ any, 44 | parent: Injector | null = null, 45 | additionalProviders: Array | null = null, 46 | name?: string, 47 | scopes = new Set(), 48 | ): R3Injector { 49 | const providers = [ 50 | additionalProviders || EMPTY_ARRAY, 51 | importProvidersFrom(defType), 52 | ]; 53 | name = name || (typeof defType === 'object' ? undefined : stringify(defType)); 54 | 55 | return new R3Injector( 56 | providers, 57 | parent || getNullInjector(), 58 | name || null, 59 | scopes, 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/import/di/forward_ref.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../interface/type'; 10 | import { getClosureSafeProperty } from '../util/property'; 11 | import { stringify } from '../util/stringify'; 12 | 13 | /** 14 | * An interface that a function passed into `forwardRef` has to implement. 15 | * 16 | * @usageNotes 17 | * ### Example 18 | * 19 | * {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref_fn'} 20 | * @publicApi 21 | */ 22 | export interface ForwardRefFn { 23 | (): any; 24 | } 25 | 26 | const __forward_ref__ = getClosureSafeProperty({ 27 | __forward_ref__: getClosureSafeProperty, 28 | }); 29 | 30 | /** 31 | * Allows to refer to references which are not yet defined. 32 | * 33 | * For instance, `forwardRef` is used when the `token` which we need to refer to for the purposes of 34 | * DI is declared, but not yet defined. It is also used when the `token` which we use when creating 35 | * a query is not yet defined. 36 | * 37 | * `forwardRef` is also used to break circularities in standalone components imports. 38 | * 39 | * @usageNotes 40 | * ### Circular dependency example 41 | * {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref'} 42 | * 43 | * ### Circular standalone reference import example 44 | * ```angular-ts 45 | * @Component({ 46 | * standalone: true, 47 | * imports: [ChildComponent], 48 | * selector: 'app-parent', 49 | * template: ``, 50 | * }) 51 | * export class ParentComponent { 52 | * @Input() hideParent: boolean; 53 | * } 54 | * 55 | * 56 | * @Component({ 57 | * standalone: true, 58 | * imports: [CommonModule, forwardRef(() => ParentComponent)], 59 | * selector: 'app-child', 60 | * template: ``, 61 | * }) 62 | * export class ChildComponent { 63 | * @Input() hideParent: boolean; 64 | * } 65 | * ``` 66 | * 67 | * @publicApi 68 | */ 69 | export function forwardRef(forwardRefFn: ForwardRefFn): Type { 70 | (forwardRefFn).__forward_ref__ = forwardRef; 71 | (forwardRefFn).toString = function () { 72 | return stringify(this()); 73 | }; 74 | return >(forwardRefFn); 75 | } 76 | 77 | /** 78 | * Lazily retrieves the reference value from a forwardRef. 79 | * 80 | * Acts as the identity function when given a non-forward-ref value. 81 | * 82 | * @usageNotes 83 | * ### Example 84 | * 85 | * {@example core/di/ts/forward_ref/forward_ref_spec.ts region='resolve_forward_ref'} 86 | * 87 | * @see {@link forwardRef} 88 | * @publicApi 89 | */ 90 | export function resolveForwardRef(type: T): T { 91 | return isForwardRef(type) ? type() : type; 92 | } 93 | 94 | /** Checks whether a function is wrapped by a `forwardRef`. */ 95 | export function isForwardRef(fn: any): fn is () => any { 96 | return ( 97 | typeof fn === 'function' && 98 | fn.hasOwnProperty(__forward_ref__) && 99 | fn.__forward_ref__ === forwardRef 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /src/import/di/index.ts: -------------------------------------------------------------------------------- 1 | export { ProviderToken } from './provider_token'; 2 | export { assertInInjectionContext, runInInjectionContext } from './contextual'; 3 | export { EnvironmentInjector } from './r3_injector'; 4 | -------------------------------------------------------------------------------- /src/import/di/initializer_token.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { InjectionToken } from './injection_token'; 10 | 11 | /** 12 | * A multi-provider token for initialization functions that will run upon construction of an 13 | * environment injector. 14 | * 15 | * @deprecated from v19.0.0, use provideEnvironmentInitializer instead 16 | * 17 | * @see {@link provideEnvironmentInitializer} 18 | * 19 | * Note: As opposed to the `APP_INITIALIZER` token, the `ENVIRONMENT_INITIALIZER` functions are not awaited, 20 | * hence they should not be `async`. 21 | * 22 | * @publicApi 23 | */ 24 | export const ENVIRONMENT_INITIALIZER = new InjectionToken< 25 | ReadonlyArray<() => void> 26 | >(''); 27 | -------------------------------------------------------------------------------- /src/import/di/inject_switch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { throwProviderNotFoundError } from '../render3/errors_di'; 10 | 11 | import { getInjectableDef, ɵɵInjectableDeclaration } from './interface/defs'; 12 | import { InjectFlags } from './interface/injector'; 13 | import { ProviderToken } from './provider_token'; 14 | 15 | /** 16 | * Current implementation of inject. 17 | * 18 | * By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed 19 | * to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this 20 | * way for two reasons: 21 | * 1. `Injector` should not depend on ivy logic. 22 | * 2. To maintain tree shake-ability we don't want to bring in unnecessary code. 23 | */ 24 | let _injectImplementation: 25 | | ((token: ProviderToken, flags?: InjectFlags) => T | null) 26 | | undefined; 27 | export function getInjectImplementation() { 28 | return _injectImplementation; 29 | } 30 | 31 | /** 32 | * Sets the current inject implementation. 33 | */ 34 | export function setInjectImplementation( 35 | impl: 36 | | ((token: ProviderToken, flags?: InjectFlags) => T | null) 37 | | undefined, 38 | ): ((token: ProviderToken, flags?: InjectFlags) => T | null) | undefined { 39 | const previous = _injectImplementation; 40 | _injectImplementation = impl; 41 | return previous; 42 | } 43 | 44 | /** 45 | * Injects `root` tokens in limp mode. 46 | * 47 | * If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to 48 | * `"root"`. This is known as the limp mode injection. In such case the value is stored in the 49 | * injectable definition. 50 | */ 51 | export function injectRootLimpMode( 52 | token: ProviderToken, 53 | notFoundValue: T | undefined, 54 | flags: InjectFlags, 55 | ): T | null { 56 | const injectableDef: ɵɵInjectableDeclaration | null = 57 | getInjectableDef(token); 58 | if (injectableDef && injectableDef.providedIn == 'root') { 59 | return injectableDef.value === undefined 60 | ? (injectableDef.value = injectableDef.factory()) 61 | : injectableDef.value; 62 | } 63 | if (flags & InjectFlags.Optional) return null; 64 | if (notFoundValue !== undefined) return notFoundValue; 65 | throwProviderNotFoundError(token, 'Injector'); 66 | } 67 | -------------------------------------------------------------------------------- /src/import/di/injectable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../interface/type'; 10 | import { TypeDecorator } from '../util/decorators'; 11 | 12 | import { 13 | ClassSansProvider, 14 | ConstructorSansProvider, 15 | ExistingSansProvider, 16 | FactorySansProvider, 17 | StaticClassSansProvider, 18 | ValueSansProvider, 19 | } from './interface/provider'; 20 | 21 | /** 22 | * Injectable providers used in `@Injectable` decorator. 23 | * 24 | * @publicApi 25 | */ 26 | export type InjectableProvider = 27 | | ValueSansProvider 28 | | ExistingSansProvider 29 | | StaticClassSansProvider 30 | | ConstructorSansProvider 31 | | FactorySansProvider 32 | | ClassSansProvider; 33 | 34 | /** 35 | * Type of the Injectable decorator / constructor function. 36 | * 37 | * @publicApi 38 | */ 39 | export interface InjectableDecorator { 40 | /** 41 | * Decorator that marks a class as available to be 42 | * provided and injected as a dependency. 43 | * 44 | * @see [Introduction to Services and DI](guide/di) 45 | * @see [Dependency Injection Guide](guide/di/dependency-injection 46 | * 47 | * @usageNotes 48 | * 49 | * Marking a class with `@Injectable` ensures that the compiler 50 | * will generate the necessary metadata to create the class's 51 | * dependencies when the class is injected. 52 | * 53 | * The following example shows how a service class is properly 54 | * marked so that a supporting service can be injected upon creation. 55 | * 56 | * {@example core/di/ts/metadata_spec.ts region='Injectable'} 57 | * 58 | */ 59 | (): TypeDecorator; 60 | ( 61 | options?: { 62 | providedIn: Type | 'root' | 'platform' | 'any' | null; 63 | } & InjectableProvider, 64 | ): TypeDecorator; 65 | new (): Injectable; 66 | new ( 67 | options?: { 68 | providedIn: Type | 'root' | 'platform' | 'any' | null; 69 | } & InjectableProvider, 70 | ): Injectable; 71 | } 72 | 73 | /** 74 | * Type of the Injectable metadata. 75 | * 76 | * @publicApi 77 | */ 78 | export interface Injectable { 79 | /** 80 | * Determines which injectors will provide the injectable. 81 | * 82 | * - `Type` - associates the injectable with an `@NgModule` or other `InjectorType`. This 83 | * option is DEPRECATED. 84 | * - 'null' : Equivalent to `undefined`. The injectable is not provided in any scope automatically 85 | * and must be added to a `providers` array of an [@NgModule](api/core/NgModule#providers), 86 | * [@Component](api/core/Directive#providers) or [@Directive](api/core/Directive#providers). 87 | * 88 | * The following options specify that this injectable should be provided in one of the following 89 | * injectors: 90 | * - 'root' : The application-level injector in most apps. 91 | * - 'platform' : A special singleton platform injector shared by all 92 | * applications on the page. 93 | * - 'any' : Provides a unique instance in each lazy loaded module while all eagerly loaded 94 | * modules share one instance. This option is DEPRECATED. 95 | * 96 | */ 97 | providedIn?: Type | 'root' | 'platform' | 'any' | null; 98 | } 99 | -------------------------------------------------------------------------------- /src/import/di/injection_token.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../interface/type'; 10 | 11 | import { ɵɵdefineInjectable } from './interface/defs'; 12 | 13 | /** 14 | * Creates a token that can be used in a DI Provider. 15 | * 16 | * Use an `InjectionToken` whenever the type you are injecting is not reified (does not have a 17 | * runtime representation) such as when injecting an interface, callable type, array or 18 | * parameterized type. 19 | * 20 | * `InjectionToken` is parameterized on `T` which is the type of object which will be returned by 21 | * the `Injector`. This provides an additional level of type safety. 22 | * 23 | *
24 | * 25 | * **Important Note**: Ensure that you use the same instance of the `InjectionToken` in both the 26 | * provider and the injection call. Creating a new instance of `InjectionToken` in different places, 27 | * even with the same description, will be treated as different tokens by Angular's DI system, 28 | * leading to a `NullInjectorError`. 29 | * 30 | *
31 | * 32 | * {@example injection-token/src/main.ts region='InjectionToken'} 33 | * 34 | * When creating an `InjectionToken`, you can optionally specify a factory function which returns 35 | * (possibly by creating) a default value of the parameterized type `T`. This sets up the 36 | * `InjectionToken` using this factory as a provider as if it was defined explicitly in the 37 | * application's root injector. If the factory function, which takes zero arguments, needs to inject 38 | * dependencies, it can do so using the [`inject`](api/core/inject) function. 39 | * As you can see in the Tree-shakable InjectionToken example below. 40 | * 41 | * Additionally, if a `factory` is specified you can also specify the `providedIn` option, which 42 | * overrides the above behavior and marks the token as belonging to a particular `@NgModule` (note: 43 | * this option is now deprecated). As mentioned above, `'root'` is the default value for 44 | * `providedIn`. 45 | * 46 | * The `providedIn: NgModule` and `providedIn: 'any'` options are deprecated. 47 | * 48 | * @usageNotes 49 | * ### Basic Examples 50 | * 51 | * ### Plain InjectionToken 52 | * 53 | * {@example core/di/ts/injector_spec.ts region='InjectionToken'} 54 | * 55 | * ### Tree-shakable InjectionToken 56 | * 57 | * {@example core/di/ts/injector_spec.ts region='ShakableInjectionToken'} 58 | * 59 | * @publicApi 60 | */ 61 | export class InjectionToken { 62 | /** @internal */ 63 | readonly ngMetadataName = 'InjectionToken'; 64 | 65 | readonly ɵprov: unknown; 66 | 67 | /** 68 | * @param _desc Description for the token, 69 | * used only for debugging purposes, 70 | * it should but does not need to be unique 71 | * @param options Options for the token's usage, as described above 72 | */ 73 | constructor( 74 | protected _desc: string, 75 | options?: { 76 | providedIn?: Type | 'root' | 'platform' | 'any' | null; 77 | factory: () => T; 78 | }, 79 | ) { 80 | this.ɵprov = undefined; 81 | if (typeof options === 'number') { 82 | // This is a special hack to assign __NG_ELEMENT_ID__ to this instance. 83 | // See `InjectorMarkers` 84 | } else if (options !== undefined) { 85 | this.ɵprov = ɵɵdefineInjectable({ 86 | token: this, 87 | providedIn: options.providedIn || 'root', 88 | factory: options.factory, 89 | }); 90 | } 91 | } 92 | 93 | /** 94 | * @internal 95 | */ 96 | get multi(): InjectionToken> { 97 | return this as InjectionToken>; 98 | } 99 | 100 | toString(): string { 101 | return `InjectionToken ${this._desc}`; 102 | } 103 | } 104 | 105 | export interface InjectableDefToken extends InjectionToken { 106 | ɵprov: unknown; 107 | } 108 | -------------------------------------------------------------------------------- /src/import/di/injector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { createInjector } from './create_injector'; 10 | import { THROW_IF_NOT_FOUND, ɵɵinject } from './injector_compatibility'; 11 | import { InjectorMarkers } from './injector_marker'; 12 | import { INJECTOR } from './injector_token'; 13 | import { ɵɵdefineInjectable } from './interface/defs'; 14 | import { InjectFlags, InjectOptions } from './interface/injector'; 15 | import { Provider, StaticProvider } from './interface/provider'; 16 | import { NullInjector } from './null_injector'; 17 | import { ProviderToken } from './provider_token'; 18 | 19 | /** 20 | * Concrete injectors implement this interface. Injectors are configured 21 | * with [providers](guide/di/dependency-injection-providers) that associate 22 | * dependencies of various types with [injection tokens](guide/di/dependency-injection-providers). 23 | * 24 | * @see [DI Providers](guide/di/dependency-injection-providers). 25 | * @see {@link StaticProvider} 26 | * 27 | * @usageNotes 28 | * 29 | * The following example creates a service injector instance. 30 | * 31 | * {@example core/di/ts/provider_spec.ts region='ConstructorProvider'} 32 | * 33 | * ### Usage example 34 | * 35 | * {@example core/di/ts/injector_spec.ts region='Injector'} 36 | * 37 | * `Injector` returns itself when given `Injector` as a token: 38 | * 39 | * {@example core/di/ts/injector_spec.ts region='injectInjector'} 40 | * 41 | * @publicApi 42 | */ 43 | export abstract class Injector { 44 | static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND; 45 | static NULL: Injector = /* @__PURE__ */ new NullInjector(); 46 | 47 | /** 48 | * Internal note on the `options?: InjectOptions|InjectFlags` override of the `get` 49 | * method: consider dropping the `InjectFlags` part in one of the major versions. 50 | * It can **not** be done in minor/patch, since it's breaking for custom injectors 51 | * that only implement the old `InjectorFlags` interface. 52 | */ 53 | 54 | /** 55 | * Retrieves an instance from the injector based on the provided token. 56 | * @returns The instance from the injector if defined, otherwise the `notFoundValue`. 57 | * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. 58 | */ 59 | abstract get( 60 | token: ProviderToken, 61 | notFoundValue: undefined, 62 | options: InjectOptions & { 63 | optional?: false; 64 | }, 65 | ): T; 66 | /** 67 | * Retrieves an instance from the injector based on the provided token. 68 | * @returns The instance from the injector if defined, otherwise the `notFoundValue`. 69 | * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. 70 | */ 71 | abstract get( 72 | token: ProviderToken, 73 | notFoundValue: null | undefined, 74 | options: InjectOptions, 75 | ): T | null; 76 | /** 77 | * Retrieves an instance from the injector based on the provided token. 78 | * @returns The instance from the injector if defined, otherwise the `notFoundValue`. 79 | * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. 80 | */ 81 | abstract get( 82 | token: ProviderToken, 83 | notFoundValue?: T, 84 | options?: InjectOptions | InjectFlags, 85 | ): T; 86 | /** 87 | * Retrieves an instance from the injector based on the provided token. 88 | * @returns The instance from the injector if defined, otherwise the `notFoundValue`. 89 | * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. 90 | * @deprecated use object-based flags (`InjectOptions`) instead. 91 | */ 92 | abstract get( 93 | token: ProviderToken, 94 | notFoundValue?: T, 95 | flags?: InjectFlags, 96 | ): T; 97 | /** 98 | * @deprecated from v4.0.0 use ProviderToken 99 | * @suppress {duplicate} 100 | */ 101 | abstract get(token: any, notFoundValue?: any): any; 102 | 103 | /** 104 | * @deprecated from v5 use the new signature Injector.create(options) 105 | */ 106 | static create(providers: StaticProvider[], parent?: Injector): Injector; 107 | 108 | /** 109 | * Creates a new injector instance that provides one or more dependencies, 110 | * according to a given type or types of `StaticProvider`. 111 | * 112 | * @param options An object with the following properties: 113 | * * `providers`: An array of providers of the [StaticProvider type](api/core/StaticProvider). 114 | * * `parent`: (optional) A parent injector. 115 | * * `name`: (optional) A developer-defined identifying name for the new injector. 116 | * 117 | * @returns The new injector instance. 118 | * 119 | */ 120 | static create(options: { 121 | providers: Array; 122 | parent?: Injector; 123 | name?: string; 124 | }): Injector; 125 | 126 | static create( 127 | options: 128 | | StaticProvider[] 129 | | { 130 | providers: Array; 131 | parent?: Injector; 132 | name?: string; 133 | }, 134 | parent?: Injector, 135 | ): Injector { 136 | if (Array.isArray(options)) { 137 | return createInjector({ name: '' }, parent, options, ''); 138 | } else { 139 | const name = options.name ?? ''; 140 | return createInjector({ name }, options.parent, options.providers, name); 141 | } 142 | } 143 | 144 | /** @nocollapse */ 145 | static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({ 146 | token: Injector, 147 | providedIn: 'any', 148 | factory: () => ɵɵinject(INJECTOR), 149 | }); 150 | 151 | /** 152 | * @internal 153 | * @nocollapse 154 | */ 155 | static __NG_ELEMENT_ID__ = InjectorMarkers.Injector; 156 | } 157 | -------------------------------------------------------------------------------- /src/import/di/injector_marker.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * Special markers which can be left on `Type.__NG_ELEMENT_ID__` which are used by the Ivy's 11 | * `NodeInjector`. Usually these markers contain factory functions. But in case of this special 12 | * marker we can't leave behind a function because it would create tree shaking problem. 13 | * 14 | * Currently only `Injector` is special. 15 | * 16 | * NOTE: the numbers here must be negative, because positive numbers are used as IDs for bloom 17 | * filter. 18 | */ 19 | export const enum InjectorMarkers { 20 | /** 21 | * Marks that the current type is `Injector` 22 | */ 23 | Injector = -1, 24 | } 25 | -------------------------------------------------------------------------------- /src/import/di/injector_token.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { InjectionToken } from './injection_token'; 10 | import type { Injector } from './injector'; 11 | import { InjectorMarkers } from './injector_marker'; 12 | 13 | /** 14 | * An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors. 15 | * 16 | * Requesting this token instead of `Injector` allows `StaticInjector` to be tree-shaken from a 17 | * project. 18 | * 19 | * @publicApi 20 | */ 21 | export const INJECTOR = new InjectionToken( 22 | '', 23 | // Disable tslint because this is const enum which gets inlined not top level prop access. 24 | // tslint:disable-next-line: no-toplevel-property-access 25 | InjectorMarkers.Injector as any, // Special value used by Ivy to identify `Injector`. 26 | ); 27 | -------------------------------------------------------------------------------- /src/import/di/interface/defs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../../interface/type'; 10 | import { getClosureSafeProperty } from '../../util/property'; 11 | 12 | import { 13 | ClassProvider, 14 | ConstructorProvider, 15 | EnvironmentProviders, 16 | ExistingProvider, 17 | FactoryProvider, 18 | StaticClassProvider, 19 | ValueProvider, 20 | } from './provider'; 21 | 22 | /** 23 | * Information about how a type or `InjectionToken` interfaces with the DI system. 24 | * 25 | * At a minimum, this includes a `factory` which defines how to create the given type `T`, possibly 26 | * requesting injection of other types if necessary. 27 | * 28 | * Optionally, a `providedIn` parameter specifies that the given type belongs to a particular 29 | * `Injector`, `NgModule`, or a special scope (e.g. `'root'`). A value of `null` indicates 30 | * that the injectable does not belong to any scope. 31 | * 32 | * @codeGenApi 33 | * @publicApi The ViewEngine compiler emits code with this type for injectables. This code is 34 | * deployed to npm, and should be treated as public api. 35 | 36 | */ 37 | export interface ɵɵInjectableDeclaration { 38 | /** 39 | * Specifies that the given type belongs to a particular injector: 40 | * - `InjectorType` such as `NgModule`, 41 | * - `'root'` the root injector 42 | * - `'any'` all injectors. 43 | * - `null`, does not belong to any injector. Must be explicitly listed in the injector 44 | * `providers`. 45 | */ 46 | providedIn: 47 | | InjectorType 48 | | 'root' 49 | | 'platform' 50 | | 'any' 51 | | 'environment' 52 | | null; 53 | 54 | /** 55 | * The token to which this definition belongs. 56 | * 57 | * Note that this may not be the same as the type that the `factory` will create. 58 | */ 59 | token: unknown; 60 | 61 | /** 62 | * Factory method to execute to create an instance of the injectable. 63 | */ 64 | factory: (t?: Type) => T; 65 | 66 | /** 67 | * In a case of no explicit injector, a location where the instance of the injectable is stored. 68 | */ 69 | value: T | undefined; 70 | } 71 | 72 | /** 73 | * Information about the providers to be included in an `Injector` as well as how the given type 74 | * which carries the information should be created by the DI system. 75 | * 76 | * An `InjectorDef` can import other types which have `InjectorDefs`, forming a deep nested 77 | * structure of providers with a defined priority (identically to how `NgModule`s also have 78 | * an import/dependency structure). 79 | * 80 | * NOTE: This is a private type and should not be exported 81 | * 82 | * @codeGenApi 83 | */ 84 | export interface ɵɵInjectorDef { 85 | // TODO(alxhub): Narrow down the type here once decorators properly change the return type of the 86 | // class they are decorating (to add the ɵprov property for example). 87 | providers: ( 88 | | Type 89 | | ValueProvider 90 | | ExistingProvider 91 | | FactoryProvider 92 | | ConstructorProvider 93 | | StaticClassProvider 94 | | ClassProvider 95 | | EnvironmentProviders 96 | | any[] 97 | )[]; 98 | 99 | imports: (InjectorType | InjectorTypeWithProviders)[]; 100 | } 101 | 102 | /** 103 | * A `Type` which has a `ɵprov: ɵɵInjectableDeclaration` static field. 104 | * 105 | * `InjectableType`s contain their own Dependency Injection metadata and are usable in an 106 | * `InjectorDef`-based `StaticInjector`. 107 | * 108 | * @publicApi 109 | */ 110 | export interface InjectableType extends Type { 111 | /** 112 | * Opaque type whose structure is highly version dependent. Do not rely on any properties. 113 | */ 114 | ɵprov: unknown; 115 | } 116 | 117 | /** 118 | * A type which has an `InjectorDef` static field. 119 | * 120 | * `InjectorTypes` can be used to configure a `StaticInjector`. 121 | * 122 | * This is an opaque type whose structure is highly version dependent. Do not rely on any 123 | * properties. 124 | * 125 | * @publicApi 126 | */ 127 | export interface InjectorType extends Type { 128 | ɵfac?: unknown; 129 | } 130 | 131 | /** 132 | * Describes the `InjectorDef` equivalent of a `ModuleWithProviders`, an `InjectorType` with an 133 | * associated array of providers. 134 | * 135 | * Objects of this type can be listed in the imports section of an `InjectorDef`. 136 | * 137 | * NOTE: This is a private type and should not be exported 138 | */ 139 | export interface InjectorTypeWithProviders { 140 | ngModule: InjectorType; 141 | providers?: ( 142 | | Type 143 | | ValueProvider 144 | | ExistingProvider 145 | | FactoryProvider 146 | | ConstructorProvider 147 | | StaticClassProvider 148 | | ClassProvider 149 | | EnvironmentProviders 150 | | any[] 151 | )[]; 152 | } 153 | 154 | /** 155 | * Construct an injectable definition which defines how a token will be constructed by the DI 156 | * system, and in which injectors (if any) it will be available. 157 | * 158 | * This should be assigned to a static `ɵprov` field on a type, which will then be an 159 | * `InjectableType`. 160 | * 161 | * Options: 162 | * * `providedIn` determines which injectors will include the injectable, by either associating it 163 | * with an `@NgModule` or other `InjectorType`, or by specifying that this injectable should be 164 | * provided in the `'root'` injector, which will be the application-level injector in most apps. 165 | * * `factory` gives the zero argument function which will create an instance of the injectable. 166 | * The factory can call [`inject`](api/core/inject) to access the `Injector` and request injection 167 | * of dependencies. 168 | * 169 | * @codeGenApi 170 | * @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm. 171 | */ 172 | export function ɵɵdefineInjectable(opts: { 173 | token: unknown; 174 | providedIn?: Type | 'root' | 'platform' | 'any' | 'environment' | null; 175 | factory: () => T; 176 | }): unknown { 177 | return { 178 | token: opts.token, 179 | providedIn: (opts.providedIn as any) || null, 180 | factory: opts.factory, 181 | value: undefined, 182 | } as ɵɵInjectableDeclaration; 183 | } 184 | 185 | /** 186 | * Construct an `InjectorDef` which configures an injector. 187 | * 188 | * This should be assigned to a static injector def (`ɵinj`) field on a type, which will then be an 189 | * `InjectorType`. 190 | * 191 | * Options: 192 | * 193 | * * `providers`: an optional array of providers to add to the injector. Each provider must 194 | * either have a factory or point to a type which has a `ɵprov` static property (the 195 | * type must be an `InjectableType`). 196 | * * `imports`: an optional array of imports of other `InjectorType`s or `InjectorTypeWithModule`s 197 | * whose providers will also be added to the injector. Locally provided types will override 198 | * providers from imports. 199 | * 200 | * @codeGenApi 201 | */ 202 | export function ɵɵdefineInjector(options: { 203 | providers?: any[]; 204 | imports?: any[]; 205 | }): unknown { 206 | return { providers: options.providers || [], imports: options.imports || [] }; 207 | } 208 | 209 | /** 210 | * Read the injectable def (`ɵprov`) for `type` in a way which is immune to accidentally reading 211 | * inherited value. 212 | * 213 | * @param type A type which may have its own (non-inherited) `ɵprov`. 214 | */ 215 | export function getInjectableDef( 216 | type: any, 217 | ): ɵɵInjectableDeclaration | null { 218 | return ( 219 | getOwnDefinition(type, NG_PROV_DEF) || { 220 | token: type, 221 | factory: () => new type(), 222 | ...type.injectOptions, 223 | } 224 | ); 225 | } 226 | 227 | export function isInjectable(type: any): boolean { 228 | return getInjectableDef(type) !== null; 229 | } 230 | 231 | /** 232 | * Return definition only if it is defined directly on `type` and is not inherited from a base 233 | * class of `type`. 234 | */ 235 | function getOwnDefinition( 236 | type: any, 237 | field: string, 238 | ): ɵɵInjectableDeclaration | null { 239 | return type.hasOwnProperty(field) ? type[field] : null; 240 | } 241 | 242 | /** 243 | * Read the injectable def (`ɵprov`) for `type` or read the `ɵprov` from one of its ancestors. 244 | * 245 | * @param type A type which may have `ɵprov`, via inheritance. 246 | * 247 | * @deprecated Will be removed in a future version of Angular, where an error will occur in the 248 | * scenario if we find the `ɵprov` on an ancestor only. 249 | */ 250 | export function getInheritedInjectableDef( 251 | type: any, 252 | ): ɵɵInjectableDeclaration | null { 253 | const def = type && (type[NG_PROV_DEF] || null); 254 | 255 | if (def) { 256 | return def; 257 | } else { 258 | return null; 259 | } 260 | } 261 | 262 | /** 263 | * Read the injector def type in a way which is immune to accidentally reading inherited value. 264 | * 265 | * @param type type which may have an injector def (`ɵinj`) 266 | */ 267 | export function getInjectorDef(type: any): ɵɵInjectorDef | null { 268 | return type && (type.hasOwnProperty(NG_INJ_DEF) || false) 269 | ? (type as any)[NG_INJ_DEF] 270 | : null; 271 | } 272 | 273 | export const NG_PROV_DEF = getClosureSafeProperty({ 274 | ɵprov: getClosureSafeProperty, 275 | }); 276 | export const NG_INJ_DEF = getClosureSafeProperty({ 277 | ɵinj: getClosureSafeProperty, 278 | }); 279 | -------------------------------------------------------------------------------- /src/import/di/interface/injector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * Special flag indicating that a decorator is of type `Inject`. It's used to make `Inject` 11 | * decorator tree-shakable (so we don't have to rely on the `instanceof` checks). 12 | * Note: this flag is not included into the `InjectFlags` since it's an internal-only API. 13 | */ 14 | export const enum DecoratorFlags { 15 | Inject = -1, 16 | } 17 | 18 | /** 19 | * Injection flags for DI. 20 | * 21 | * @publicApi 22 | * @deprecated use an options object for [`inject`](api/core/inject) instead. 23 | */ 24 | export enum InjectFlags { 25 | // TODO(alxhub): make this 'const' (and remove `InternalInjectFlags` enum) when ngc no longer 26 | // writes exports of it into ngfactory files. 27 | 28 | /** Check self and check parent injector if needed */ 29 | Default = 0b0000, 30 | 31 | /** Don't ascend to ancestors of the node requesting injection. */ 32 | Self = 0b0010, 33 | 34 | /** Skip the node that is requesting injection. */ 35 | SkipSelf = 0b0100, 36 | 37 | /** Inject `defaultValue` instead if token not found. */ 38 | Optional = 0b1000, 39 | } 40 | 41 | /** 42 | * This enum is an exact copy of the `InjectFlags` enum above, but the difference is that this is a 43 | * const enum, so actual enum values would be inlined in generated code. The `InjectFlags` enum can 44 | * be turned into a const enum when ViewEngine is removed (see TODO at the `InjectFlags` enum 45 | * above). The benefit of inlining is that we can use these flags at the top level without affecting 46 | * tree-shaking (see "no-toplevel-property-access" tslint rule for more info). 47 | * Keep this enum in sync with `InjectFlags` enum above. 48 | */ 49 | export const enum InternalInjectFlags { 50 | /** Check self and check parent injector if needed */ 51 | Default = 0b0000, 52 | 53 | /** Don't ascend to ancestors of the node requesting injection. */ 54 | Self = 0b0010, 55 | 56 | /** Skip the node that is requesting injection. */ 57 | SkipSelf = 0b0100, 58 | 59 | /** Inject `defaultValue` instead if token not found. */ 60 | Optional = 0b1000, 61 | } 62 | 63 | /** 64 | * Type of the options argument to [`inject`](api/core/inject). 65 | * 66 | * @publicApi 67 | */ 68 | export interface InjectOptions { 69 | /** 70 | * Use optional injection, and return `null` if the requested token is not found. 71 | */ 72 | optional?: boolean; 73 | 74 | /** 75 | * Start injection at the parent of the current injector. 76 | */ 77 | skipSelf?: boolean; 78 | 79 | /** 80 | * Only query the current injector for the token, and don't fall back to the parent injector if 81 | * it's not found. 82 | */ 83 | self?: boolean; 84 | 85 | /** 86 | * Stop injection at the host component's injector. Only relevant when injecting from an element 87 | * injector, and a no-op for environment injectors. 88 | */ 89 | host?: boolean; 90 | } 91 | -------------------------------------------------------------------------------- /src/import/di/internal_tokens.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../interface/type'; 10 | 11 | import { InjectionToken } from './injection_token'; 12 | 13 | export const INJECTOR_DEF_TYPES = new InjectionToken< 14 | ReadonlyArray> 15 | >(''); 16 | -------------------------------------------------------------------------------- /src/import/di/metadata.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { makeParamDecorator } from '../util/decorators'; 10 | 11 | import { attachInjectFlag } from './injector_compatibility'; 12 | import { DecoratorFlags, InternalInjectFlags } from './interface/injector'; 13 | 14 | /** 15 | * Type of the Inject decorator / constructor function. 16 | * 17 | * @publicApi 18 | */ 19 | export interface InjectDecorator { 20 | /** 21 | * Parameter decorator on a dependency parameter of a class constructor 22 | * that specifies a custom provider of the dependency. 23 | * 24 | * @usageNotes 25 | * The following example shows a class constructor that specifies a 26 | * custom provider of a dependency using the parameter decorator. 27 | * 28 | * When `@Inject()` is not present, the injector uses the type annotation of the 29 | * parameter as the provider. 30 | * 31 | * {@example core/di/ts/metadata_spec.ts region='InjectWithoutDecorator'} 32 | * 33 | * @see [Dependency Injection Guide](guide/di/dependency-injection 34 | * 35 | */ 36 | (token: any): any; 37 | new (token: any): Inject; 38 | } 39 | 40 | /** 41 | * Type of the Inject metadata. 42 | * 43 | * @publicApi 44 | */ 45 | export interface Inject { 46 | /** 47 | * A DI token that maps to the dependency to be injected. 48 | */ 49 | token: any; 50 | } 51 | 52 | /** 53 | * Inject decorator and metadata. 54 | * 55 | * @Annotation 56 | * @publicApi 57 | */ 58 | export const Inject: InjectDecorator = attachInjectFlag( 59 | // Disable tslint because `DecoratorFlags` is a const enum which gets inlined. 60 | makeParamDecorator('Inject', (token: any) => ({ token })), 61 | // tslint:disable-next-line: no-toplevel-property-access 62 | DecoratorFlags.Inject, 63 | ); 64 | 65 | /** 66 | * Type of the Optional decorator / constructor function. 67 | * 68 | * @publicApi 69 | */ 70 | export interface OptionalDecorator { 71 | /** 72 | * Parameter decorator to be used on constructor parameters, 73 | * which marks the parameter as being an optional dependency. 74 | * The DI framework provides `null` if the dependency is not found. 75 | * 76 | * Can be used together with other parameter decorators 77 | * that modify how dependency injection operates. 78 | * 79 | * @usageNotes 80 | * 81 | * The following code allows the possibility of a `null` result: 82 | * 83 | * {@example core/di/ts/metadata_spec.ts region='Optional'} 84 | * 85 | * @see [Dependency Injection Guide](guide/di/dependency-injection. 86 | */ 87 | (): any; 88 | new (): Optional; 89 | } 90 | 91 | /** 92 | * Type of the Optional metadata. 93 | * 94 | * @publicApi 95 | */ 96 | export interface Optional {} 97 | 98 | /** 99 | * Optional decorator and metadata. 100 | * 101 | * @Annotation 102 | * @publicApi 103 | */ 104 | export const Optional: OptionalDecorator = 105 | // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. 106 | // tslint:disable-next-line: no-toplevel-property-access 107 | attachInjectFlag( 108 | makeParamDecorator('Optional'), 109 | InternalInjectFlags.Optional, 110 | ); 111 | 112 | /** 113 | * Type of the Self decorator / constructor function. 114 | * 115 | * @publicApi 116 | */ 117 | export interface SelfDecorator { 118 | /** 119 | * Parameter decorator to be used on constructor parameters, 120 | * which tells the DI framework to start dependency resolution from the local injector. 121 | * 122 | * Resolution works upward through the injector hierarchy, so the children 123 | * of this class must configure their own providers or be prepared for a `null` result. 124 | * 125 | * @usageNotes 126 | * 127 | * In the following example, the dependency can be resolved 128 | * by the local injector when instantiating the class itself, but not 129 | * when instantiating a child. 130 | * 131 | * {@example core/di/ts/metadata_spec.ts region='Self'} 132 | * 133 | * @see {@link SkipSelf} 134 | * @see {@link Optional} 135 | * 136 | */ 137 | (): any; 138 | new (): Self; 139 | } 140 | 141 | /** 142 | * Type of the Self metadata. 143 | * 144 | * @publicApi 145 | */ 146 | export interface Self {} 147 | 148 | /** 149 | * Self decorator and metadata. 150 | * 151 | * @Annotation 152 | * @publicApi 153 | */ 154 | export const Self: SelfDecorator = 155 | // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. 156 | // tslint:disable-next-line: no-toplevel-property-access 157 | attachInjectFlag(makeParamDecorator('Self'), InternalInjectFlags.Self); 158 | 159 | /** 160 | * Type of the `SkipSelf` decorator / constructor function. 161 | * 162 | * @publicApi 163 | */ 164 | export interface SkipSelfDecorator { 165 | /** 166 | * Parameter decorator to be used on constructor parameters, 167 | * which tells the DI framework to start dependency resolution from the parent injector. 168 | * Resolution works upward through the injector hierarchy, so the local injector 169 | * is not checked for a provider. 170 | * 171 | * @usageNotes 172 | * 173 | * In the following example, the dependency can be resolved when 174 | * instantiating a child, but not when instantiating the class itself. 175 | * 176 | * {@example core/di/ts/metadata_spec.ts region='SkipSelf'} 177 | * 178 | * @see [Dependency Injection guide](guide/di/di-in-action#skip). 179 | * @see {@link Self} 180 | * @see {@link Optional} 181 | * 182 | */ 183 | (): any; 184 | new (): SkipSelf; 185 | } 186 | 187 | /** 188 | * Type of the `SkipSelf` metadata. 189 | * 190 | * @publicApi 191 | */ 192 | export interface SkipSelf {} 193 | 194 | /** 195 | * `SkipSelf` decorator and metadata. 196 | * 197 | * @Annotation 198 | * @publicApi 199 | */ 200 | export const SkipSelf: SkipSelfDecorator = 201 | // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. 202 | // tslint:disable-next-line: no-toplevel-property-access 203 | attachInjectFlag( 204 | makeParamDecorator('SkipSelf'), 205 | InternalInjectFlags.SkipSelf, 206 | ); 207 | -------------------------------------------------------------------------------- /src/import/di/null_injector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { stringify } from '../util/stringify'; 10 | import type { Injector } from './injector'; 11 | import { THROW_IF_NOT_FOUND } from './injector_compatibility'; 12 | 13 | export class NullInjector implements Injector { 14 | get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any { 15 | if (notFoundValue === THROW_IF_NOT_FOUND) { 16 | const error = new Error( 17 | `NullInjectorError: No provider for ${stringify(token)}!`, 18 | ); 19 | error.name = 'NullInjectorError'; 20 | throw error; 21 | } 22 | return notFoundValue; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/import/di/provider_collection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../interface/type'; 10 | 11 | import { getClosureSafeProperty } from '../util/property'; 12 | 13 | import { ENVIRONMENT_INITIALIZER } from './initializer_token'; 14 | import { InjectorType, InjectorTypeWithProviders } from './interface/defs'; 15 | import { 16 | ClassProvider, 17 | ConstructorProvider, 18 | EnvironmentProviders, 19 | ExistingProvider, 20 | FactoryProvider, 21 | InternalEnvironmentProviders, 22 | ModuleWithProviders, 23 | Provider, 24 | StaticClassProvider, 25 | TypeProvider, 26 | ValueProvider, 27 | } from './interface/provider'; 28 | 29 | /** 30 | * Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally 31 | * referenced in `@Component` in a component injector. 32 | */ 33 | export function makeEnvironmentProviders( 34 | providers: (Provider | EnvironmentProviders)[], 35 | ): EnvironmentProviders { 36 | return { 37 | ɵproviders: providers, 38 | } as unknown as EnvironmentProviders; 39 | } 40 | 41 | /** 42 | * @description 43 | * This function is used to provide initialization functions that will be executed upon construction 44 | * of an environment injector. 45 | * 46 | * Note that the provided initializer is run in the injection context. 47 | * 48 | * Previously, this was achieved using the `ENVIRONMENT_INITIALIZER` token which is now deprecated. 49 | * 50 | * @see {@link ENVIRONMENT_INITIALIZER} 51 | * 52 | * @usageNotes 53 | * The following example illustrates how to configure an initialization function using 54 | * `provideEnvironmentInitializer()` 55 | * ```ts 56 | * createEnvironmentInjector( 57 | * [ 58 | * provideEnvironmentInitializer(() => { 59 | * console.log('environment initialized'); 60 | * }), 61 | * ], 62 | * parentInjector 63 | * ); 64 | * ``` 65 | * 66 | * @publicApi 67 | */ 68 | export function provideEnvironmentInitializer( 69 | initializerFn: () => void, 70 | ): EnvironmentProviders { 71 | return makeEnvironmentProviders([ 72 | { 73 | provide: ENVIRONMENT_INITIALIZER, 74 | multi: true, 75 | useValue: initializerFn, 76 | }, 77 | ]); 78 | } 79 | 80 | /** 81 | * A source of providers for the `importProvidersFrom` function. 82 | * 83 | * @publicApi 84 | */ 85 | export type ImportProvidersSource = 86 | | Type 87 | | ModuleWithProviders 88 | | Array; 89 | 90 | type WalkProviderTreeVisitor = ( 91 | provider: SingleProvider, 92 | container: Type | InjectorType, 93 | ) => void; 94 | 95 | /** 96 | * Collects providers from all NgModules and standalone components, including transitively imported 97 | * ones. 98 | * 99 | * Providers extracted via `importProvidersFrom` are only usable in an application injector or 100 | * another environment injector (such as a route injector). They should not be used in component 101 | * providers. 102 | * 103 | * More information about standalone components can be found in [this 104 | * guide](guide/components/importing). 105 | * 106 | * @usageNotes 107 | * The results of the `importProvidersFrom` call can be used in the `bootstrapApplication` call: 108 | * 109 | * ```ts 110 | * await bootstrapApplication(RootComponent, { 111 | * providers: [ 112 | * importProvidersFrom(NgModuleOne, NgModuleTwo) 113 | * ] 114 | * }); 115 | * ``` 116 | * 117 | * You can also use the `importProvidersFrom` results in the `providers` field of a route, when a 118 | * standalone component is used: 119 | * 120 | * ```ts 121 | * export const ROUTES: Route[] = [ 122 | * { 123 | * path: 'foo', 124 | * providers: [ 125 | * importProvidersFrom(NgModuleOne, NgModuleTwo) 126 | * ], 127 | * component: YourStandaloneComponent 128 | * } 129 | * ]; 130 | * ``` 131 | * 132 | * @returns Collected providers from the specified list of types. 133 | * @publicApi 134 | */ 135 | export function importProvidersFrom( 136 | ...sources: ImportProvidersSource[] 137 | ): EnvironmentProviders { 138 | return { 139 | ɵproviders: internalImportProvidersFrom(true, sources), 140 | ɵfromNgModule: true, 141 | } as InternalEnvironmentProviders; 142 | } 143 | 144 | export function internalImportProvidersFrom( 145 | checkForStandaloneCmp: boolean, 146 | ...sources: ImportProvidersSource[] 147 | ): Provider[] { 148 | const providersOut: SingleProvider[] = []; 149 | const dedup = new Set>(); // already seen types 150 | let injectorTypesWithProviders: 151 | | InjectorTypeWithProviders[] 152 | | undefined; 153 | 154 | const collectProviders: WalkProviderTreeVisitor = (provider) => { 155 | providersOut.push(provider); 156 | }; 157 | 158 | // Collect all providers from `ModuleWithProviders` types. 159 | if (injectorTypesWithProviders !== undefined) { 160 | processInjectorTypesWithProviders( 161 | injectorTypesWithProviders, 162 | collectProviders, 163 | ); 164 | } 165 | 166 | return providersOut; 167 | } 168 | 169 | /** 170 | * Collects all providers from the list of `ModuleWithProviders` and appends them to the provided 171 | * array. 172 | */ 173 | function processInjectorTypesWithProviders( 174 | typesWithProviders: InjectorTypeWithProviders[], 175 | visitor: WalkProviderTreeVisitor, 176 | ): void { 177 | for (let i = 0; i < typesWithProviders.length; i++) { 178 | const { ngModule, providers } = typesWithProviders[i]; 179 | } 180 | } 181 | 182 | /** 183 | * Internal type for a single provider in a deep provider array. 184 | */ 185 | export type SingleProvider = 186 | | TypeProvider 187 | | ValueProvider 188 | | ClassProvider 189 | | ConstructorProvider 190 | | ExistingProvider 191 | | FactoryProvider 192 | | StaticClassProvider; 193 | 194 | export const USE_VALUE = getClosureSafeProperty({ 195 | provide: String, 196 | useValue: getClosureSafeProperty, 197 | }); 198 | 199 | export function isValueProvider(value: SingleProvider): value is ValueProvider { 200 | return value !== null && typeof value === 'object' && USE_VALUE in value; 201 | } 202 | 203 | export function isExistingProvider( 204 | value: SingleProvider, 205 | ): value is ExistingProvider { 206 | return !!(value && (value as ExistingProvider).useExisting); 207 | } 208 | 209 | export function isFactoryProvider( 210 | value: SingleProvider, 211 | ): value is FactoryProvider { 212 | return !!(value && (value as FactoryProvider).useFactory); 213 | } 214 | 215 | export function isTypeProvider(value: SingleProvider): value is TypeProvider { 216 | return typeof value === 'function'; 217 | } 218 | 219 | export function isClassProvider(value: SingleProvider): value is ClassProvider { 220 | return !!(value as StaticClassProvider | ClassProvider).useClass; 221 | } 222 | -------------------------------------------------------------------------------- /src/import/di/provider_token.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { AbstractType, Type } from '../interface/type'; 10 | import { InjectionToken } from './injection_token'; 11 | 12 | /** 13 | * @description 14 | * 15 | * Token that can be used to retrieve an instance from an injector or through a query. 16 | * 17 | * @publicApi 18 | */ 19 | export type ProviderToken = Type | AbstractType | InjectionToken; 20 | -------------------------------------------------------------------------------- /src/import/di/scope.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { InjectionToken } from './injection_token'; 10 | 11 | export type InjectorScope = 'root' | 'platform' | 'environment'; 12 | 13 | /** 14 | * An internal token whose presence in an injector indicates that the injector should treat itself 15 | * as a root scoped injector when processing requests for unknown tokens which may indicate 16 | * they are provided in the root scope. 17 | */ 18 | export const INJECTOR_SCOPE = new InjectionToken(''); 19 | -------------------------------------------------------------------------------- /src/import/error_handler.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * Provides a hook for centralized exception handling. 11 | * 12 | * The default implementation of `ErrorHandler` prints error messages to the `console`. To 13 | * intercept error handling, write a custom exception handler that replaces this default as 14 | * appropriate for your app. 15 | * 16 | * @usageNotes 17 | * ### Example 18 | * 19 | * ```ts 20 | * class MyErrorHandler implements ErrorHandler { 21 | * handleError(error) { 22 | * // do something with the exception 23 | * } 24 | * } 25 | * 26 | * // Provide in standalone apps 27 | * bootstrapApplication(AppComponent, { 28 | * providers: [{provide: ErrorHandler, useClass: MyErrorHandler}] 29 | * }) 30 | * 31 | * // Provide in module-based apps 32 | * @NgModule({ 33 | * providers: [{provide: ErrorHandler, useClass: MyErrorHandler}] 34 | * }) 35 | * class MyModule {} 36 | * ``` 37 | * 38 | * @publicApi 39 | */ 40 | export class ErrorHandler { 41 | /** 42 | * @internal 43 | */ 44 | _console: Console = console; 45 | 46 | handleError(error: any): void { 47 | this._console.error('ERROR', error); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/import/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * The list of error codes used in runtime code of the `core` package. 11 | * Reserved error code range: 100-999. 12 | * 13 | * Note: the minus sign denotes the fact that a particular code has a detailed guide on 14 | * angular.io. This extra annotation is needed to avoid introducing a separate set to store 15 | * error codes which have guides, which might leak into runtime code. 16 | * 17 | * Full list of available error guides can be found at https://angular.dev/errors. 18 | * 19 | * Error code ranges per package: 20 | * - core (this package): 100-999 21 | * - forms: 1000-1999 22 | * - common: 2000-2999 23 | * - animations: 3000-3999 24 | * - router: 4000-4999 25 | * - platform-browser: 5000-5500 26 | */ 27 | export const enum RuntimeErrorCode { 28 | // Change Detection Errors 29 | EXPRESSION_CHANGED_AFTER_CHECKED = -100, 30 | RECURSIVE_APPLICATION_REF_TICK = 101, 31 | INFINITE_CHANGE_DETECTION = 103, 32 | 33 | // Dependency Injection Errors 34 | CYCLIC_DI_DEPENDENCY = -200, 35 | PROVIDER_NOT_FOUND = -201, 36 | INVALID_FACTORY_DEPENDENCY = 202, 37 | MISSING_INJECTION_CONTEXT = -203, 38 | INVALID_INJECTION_TOKEN = 204, 39 | INJECTOR_ALREADY_DESTROYED = 205, 40 | PROVIDER_IN_WRONG_CONTEXT = 207, 41 | MISSING_INJECTION_TOKEN = 208, 42 | INVALID_MULTI_PROVIDER = -209, 43 | MISSING_DOCUMENT = 210, 44 | 45 | // Template Errors 46 | MULTIPLE_COMPONENTS_MATCH = -300, 47 | EXPORT_NOT_FOUND = -301, 48 | PIPE_NOT_FOUND = -302, 49 | UNKNOWN_BINDING = 303, 50 | UNKNOWN_ELEMENT = 304, 51 | TEMPLATE_STRUCTURE_ERROR = 305, 52 | INVALID_EVENT_BINDING = 306, 53 | HOST_DIRECTIVE_UNRESOLVABLE = 307, 54 | HOST_DIRECTIVE_NOT_STANDALONE = 308, 55 | DUPLICATE_DIRECTIVE = 309, 56 | HOST_DIRECTIVE_COMPONENT = 310, 57 | HOST_DIRECTIVE_UNDEFINED_BINDING = 311, 58 | HOST_DIRECTIVE_CONFLICTING_ALIAS = 312, 59 | MULTIPLE_MATCHING_PIPES = 313, 60 | UNINITIALIZED_LET_ACCESS = 314, 61 | 62 | // Bootstrap Errors 63 | MULTIPLE_PLATFORMS = 400, 64 | PLATFORM_NOT_FOUND = 401, 65 | MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP = 402, 66 | BOOTSTRAP_COMPONENTS_NOT_FOUND = -403, 67 | PLATFORM_ALREADY_DESTROYED = 404, 68 | ASYNC_INITIALIZERS_STILL_RUNNING = 405, 69 | APPLICATION_REF_ALREADY_DESTROYED = 406, 70 | RENDERER_NOT_FOUND = 407, 71 | PROVIDED_BOTH_ZONE_AND_ZONELESS = 408, 72 | 73 | // Hydration Errors 74 | HYDRATION_NODE_MISMATCH = -500, 75 | HYDRATION_MISSING_SIBLINGS = -501, 76 | HYDRATION_MISSING_NODE = -502, 77 | UNSUPPORTED_PROJECTION_DOM_NODES = -503, 78 | INVALID_SKIP_HYDRATION_HOST = -504, 79 | MISSING_HYDRATION_ANNOTATIONS = -505, 80 | HYDRATION_STABLE_TIMEDOUT = -506, 81 | MISSING_SSR_CONTENT_INTEGRITY_MARKER = -507, 82 | MISCONFIGURED_INCREMENTAL_HYDRATION = 508, 83 | 84 | // Signal Errors 85 | SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT = 600, 86 | REQUIRE_SYNC_WITHOUT_SYNC_EMIT = 601, 87 | ASSERTION_NOT_INSIDE_REACTIVE_CONTEXT = -602, 88 | 89 | // Styling Errors 90 | 91 | // Declarations Errors 92 | 93 | // i18n Errors 94 | INVALID_I18N_STRUCTURE = 700, 95 | MISSING_LOCALE_DATA = 701, 96 | 97 | // Defer errors (750-799 range) 98 | DEFER_LOADING_FAILED = -750, 99 | 100 | // standalone errors 101 | IMPORT_PROVIDERS_FROM_STANDALONE = 800, 102 | 103 | // JIT Compilation Errors 104 | // Other 105 | INVALID_DIFFER_INPUT = 900, 106 | NO_SUPPORTING_DIFFER_FACTORY = 901, 107 | VIEW_ALREADY_ATTACHED = 902, 108 | INVALID_INHERITANCE = 903, 109 | UNSAFE_VALUE_IN_RESOURCE_URL = 904, 110 | UNSAFE_VALUE_IN_SCRIPT = 905, 111 | MISSING_GENERATED_DEF = 906, 112 | TYPE_IS_NOT_STANDALONE = 907, 113 | MISSING_ZONEJS = 908, 114 | UNEXPECTED_ZONE_STATE = 909, 115 | UNSAFE_IFRAME_ATTRS = -910, 116 | VIEW_ALREADY_DESTROYED = 911, 117 | COMPONENT_ID_COLLISION = -912, 118 | IMAGE_PERFORMANCE_WARNING = -913, 119 | UNEXPECTED_ZONEJS_PRESENT_IN_ZONELESS_MODE = 914, 120 | 121 | // Signal integration errors 122 | REQUIRED_INPUT_NO_VALUE = -950, 123 | REQUIRED_QUERY_NO_VALUE = -951, 124 | REQUIRED_MODEL_NO_VALUE = 952, 125 | 126 | // Output() 127 | OUTPUT_REF_DESTROYED = 953, 128 | 129 | // Repeater errors 130 | LOOP_TRACK_DUPLICATE_KEYS = -955, 131 | LOOP_TRACK_RECREATE = -956, 132 | 133 | // Runtime dependency tracker errors 134 | RUNTIME_DEPS_INVALID_IMPORTED_TYPE = 980, 135 | RUNTIME_DEPS_ORPHAN_COMPONENT = 981, 136 | 137 | // Upper bounds for core runtime errors is 999 138 | } 139 | 140 | /** 141 | * Class that represents a runtime error. 142 | * Formats and outputs the error message in a consistent way. 143 | * 144 | * Example: 145 | * ```ts 146 | * throw new RuntimeError( 147 | * RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED, 148 | * ngDevMode && 'Injector has already been destroyed.'); 149 | * ``` 150 | * 151 | * Note: the `message` argument contains a descriptive error message as a string in development 152 | * mode (when the `ngDevMode` is defined). In production mode (after tree-shaking pass), the 153 | * `message` argument becomes `false`, thus we account for it in the typings and the runtime 154 | * logic. 155 | */ 156 | export class RuntimeError extends Error { 157 | constructor( 158 | public code: T, 159 | message: null | false | string, 160 | ) { 161 | super(formatRuntimeError(code, message)); 162 | } 163 | } 164 | 165 | /** 166 | * Called to format a runtime error. 167 | * See additional info on the `message` argument type in the `RuntimeError` class description. 168 | */ 169 | export function formatRuntimeError( 170 | code: T, 171 | message: null | false | string, 172 | ): string { 173 | // Error code might be a negative number, which is a special marker that instructs the logic to 174 | // generate a link to the error details page on angular.io. 175 | // We also prepend `0` to non-compile-time errors. 176 | const fullCode = `NG0${Math.abs(code)}`; 177 | 178 | const errorMessage = `${fullCode}${message ? ': ' + message : ''}`; 179 | 180 | if (false) { 181 | } 182 | return errorMessage; 183 | } 184 | -------------------------------------------------------------------------------- /src/import/index.ts: -------------------------------------------------------------------------------- 1 | import type { InjectableDecorator } from './di/injectable'; 2 | import { Injector } from './di/injector'; 3 | import { EnvironmentProviders, Provider } from './di/interface/provider'; 4 | export { EnvironmentProviders, Provider } from './di/interface/provider'; 5 | import { getNullInjector, R3Injector } from './di/r3_injector'; 6 | import { INJECTOR_SCOPE, InjectorScope } from './di/scope'; 7 | 8 | export * from './di/injectable'; 9 | export * from './di/metadata'; 10 | export * from './di/r3_injector'; 11 | export * from './di/interface/defs'; 12 | export * from './di/injector_compatibility'; 13 | export * from './di/injection_token'; 14 | export * from './di/null_injector'; 15 | export * from './di/injector'; 16 | export * from './di/interface/injector'; 17 | export * from './di/scope'; 18 | export * from './render3/instructions/di'; 19 | 20 | export * from './core_reactivity_export_internal'; 21 | export * from './change_detection/scheduling/zoneless_scheduling'; 22 | export * from './change_detection/scheduling/zoneless_scheduling_impl'; 23 | 24 | export * from './resource'; 25 | export * from './di/provider_token'; 26 | export class StaticInjectOptions { 27 | static injectOptions: Parameters[0]; 28 | } 29 | export class RootStaticInjectOptions { 30 | static injectOptions: Parameters[0] = { 31 | providedIn: 'root', 32 | }; 33 | } 34 | 35 | export function createInjector(options: { 36 | providers: Array; 37 | parent: Injector; 38 | name?: string; 39 | scopes?: Set; 40 | }) { 41 | return new R3Injector( 42 | options.providers, 43 | options.parent ?? getNullInjector(), 44 | options.name ?? '', 45 | options.scopes ?? new Set([]), 46 | ); 47 | } 48 | export function createRootInjector(options: { 49 | providers: Array; 50 | name?: string; 51 | scopes?: Set; 52 | }) { 53 | return new R3Injector( 54 | [ 55 | ...options.providers, 56 | { 57 | provide: INJECTOR_SCOPE, 58 | useValue: 'root', 59 | }, 60 | ], 61 | getNullInjector(), 62 | options.name ?? '', 63 | options.scopes ?? new Set([]), 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/import/interface/lifecycle_hooks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * A lifecycle hook that is called when a directive, pipe, or service is destroyed. 11 | * Use for any custom cleanup that needs to occur when the 12 | * instance is destroyed. 13 | * @see [Lifecycle hooks guide](guide/components/lifecycle) 14 | * 15 | * @usageNotes 16 | * The following snippet shows how a component can implement this interface 17 | * to define its own custom clean-up method. 18 | * 19 | * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnDestroy'} 20 | * 21 | * @publicApi 22 | */ 23 | export interface OnDestroy { 24 | /** 25 | * A callback method that performs custom clean-up, invoked immediately 26 | * before a directive, pipe, or service instance is destroyed. 27 | */ 28 | ngOnDestroy(): void; 29 | } 30 | -------------------------------------------------------------------------------- /src/import/interface/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * @description 11 | * 12 | * Represents a type that a Component or other object is instances of. 13 | * 14 | * An example of a `Type` is `MyCustomComponent` class, which in JavaScript is represented by 15 | * the `MyCustomComponent` constructor function. 16 | * 17 | * @publicApi 18 | */ 19 | export const Type = Function; 20 | 21 | export function isType(v: any): v is Type { 22 | return typeof v === 'function'; 23 | } 24 | 25 | /** 26 | * @description 27 | * 28 | * Represents an abstract class `T`, if applied to a concrete class it would stop being 29 | * instantiable. 30 | * 31 | * @publicApi 32 | */ 33 | export interface AbstractType extends Function { 34 | prototype: T; 35 | } 36 | 37 | export interface Type extends Function { 38 | new (...args: any[]): T; 39 | } 40 | 41 | /** 42 | * Returns a writable type version of type. 43 | * 44 | * USAGE: 45 | * Given: 46 | * ```ts 47 | * interface Person {readonly name: string} 48 | * ``` 49 | * 50 | * We would like to get a read/write version of `Person`. 51 | * ```ts 52 | * const WritablePerson = Writable; 53 | * ``` 54 | * 55 | * The result is that you can do: 56 | * 57 | * ```ts 58 | * const readonlyPerson: Person = {name: 'Marry'}; 59 | * readonlyPerson.name = 'John'; // TypeError 60 | * (readonlyPerson as WritablePerson).name = 'John'; // OK 61 | * 62 | * // Error: Correctly detects that `Person` did not have `age` property. 63 | * (readonlyPerson as WritablePerson).age = 30; 64 | * ``` 65 | */ 66 | export type Writable = { 67 | -readonly [K in keyof T]: T[K]; 68 | }; 69 | -------------------------------------------------------------------------------- /src/import/linker/destroy_ref.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { EnvironmentInjector } from '../di'; 10 | 11 | /** 12 | * `DestroyRef` lets you set callbacks to run for any cleanup or destruction behavior. 13 | * The scope of this destruction depends on where `DestroyRef` is injected. If `DestroyRef` 14 | * is injected in a component or directive, the callbacks run when that component or 15 | * directive is destroyed. Otherwise the callbacks run when a corresponding injector is destroyed. 16 | * 17 | * @publicApi 18 | */ 19 | export abstract class DestroyRef { 20 | // Here the `DestroyRef` acts primarily as a DI token. There are (currently) types of objects that 21 | // can be returned from the injector when asking for this token: 22 | // - `NodeInjectorDestroyRef` when retrieved from a node injector; 23 | // - `EnvironmentInjector` when retrieved from an environment injector 24 | 25 | /** 26 | * Registers a destroy callback in a given lifecycle scope. Returns a cleanup function that can 27 | * be invoked to unregister the callback. 28 | * 29 | * @usageNotes 30 | * ### Example 31 | * ```ts 32 | * const destroyRef = inject(DestroyRef); 33 | * 34 | * // register a destroy callback 35 | * const unregisterFn = destroyRef.onDestroy(() => doSomethingOnDestroy()); 36 | * 37 | * // stop the destroy callback from executing if needed 38 | * unregisterFn(); 39 | * ``` 40 | */ 41 | abstract onDestroy(callback: () => void): () => void; 42 | 43 | /** 44 | * @internal 45 | * @nocollapse 46 | */ 47 | 48 | /** 49 | * @internal 50 | * @nocollapse 51 | */ 52 | static __NG_ENV_ID__: (injector: EnvironmentInjector) => DestroyRef = ( 53 | injector, 54 | ) => injector; 55 | } 56 | -------------------------------------------------------------------------------- /src/import/pending_tasks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { BehaviorSubject } from 'rxjs'; 10 | 11 | import { inject } from './di/injector_compatibility'; 12 | import { ɵɵdefineInjectable } from './di/interface/defs'; 13 | import { OnDestroy } from './interface/lifecycle_hooks'; 14 | import { 15 | ChangeDetectionScheduler, 16 | NotificationSource, 17 | } from './change_detection/scheduling/zoneless_scheduling'; 18 | 19 | /** 20 | * Internal implementation of the pending tasks service. 21 | */ 22 | export class PendingTasksInternal implements OnDestroy { 23 | private taskId = 0; 24 | private pendingTasks = new Set(); 25 | private get _hasPendingTasks() { 26 | return this.hasPendingTasks.value; 27 | } 28 | hasPendingTasks = new BehaviorSubject(false); 29 | 30 | add(): number { 31 | if (!this._hasPendingTasks) { 32 | this.hasPendingTasks.next(true); 33 | } 34 | const taskId = this.taskId++; 35 | this.pendingTasks.add(taskId); 36 | return taskId; 37 | } 38 | 39 | has(taskId: number): boolean { 40 | return this.pendingTasks.has(taskId); 41 | } 42 | 43 | remove(taskId: number): void { 44 | this.pendingTasks.delete(taskId); 45 | if (this.pendingTasks.size === 0 && this._hasPendingTasks) { 46 | this.hasPendingTasks.next(false); 47 | } 48 | } 49 | 50 | ngOnDestroy(): void { 51 | this.pendingTasks.clear(); 52 | if (this._hasPendingTasks) { 53 | this.hasPendingTasks.next(false); 54 | } 55 | } 56 | 57 | /** @nocollapse */ 58 | static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({ 59 | token: PendingTasksInternal, 60 | providedIn: 'root', 61 | factory: () => new PendingTasksInternal(), 62 | }); 63 | } 64 | 65 | /** 66 | * Service that keeps track of pending tasks contributing to the stableness of Angular 67 | * application. While several existing Angular services (ex.: `HttpClient`) will internally manage 68 | * tasks influencing stability, this API gives control over stability to library and application 69 | * developers for specific cases not covered by Angular internals. 70 | * 71 | * The concept of stability comes into play in several important scenarios: 72 | * - SSR process needs to wait for the application stability before serializing and sending rendered 73 | * HTML; 74 | * - tests might want to delay assertions until the application becomes stable; 75 | * 76 | * @usageNotes 77 | * ```ts 78 | * const pendingTasks = inject(PendingTasks); 79 | * const taskCleanup = pendingTasks.add(); 80 | * // do work that should block application's stability and then: 81 | * taskCleanup(); 82 | * ``` 83 | * 84 | * @publicApi 85 | * @developerPreview 86 | */ 87 | export class PendingTasks { 88 | private internalPendingTasks = inject(PendingTasksInternal); 89 | private scheduler = inject(ChangeDetectionScheduler); 90 | /** 91 | * Adds a new task that should block application's stability. 92 | * @returns A cleanup function that removes a task when called. 93 | */ 94 | add(): () => void { 95 | const taskId = this.internalPendingTasks.add(); 96 | return () => { 97 | if (!this.internalPendingTasks.has(taskId)) { 98 | // This pending task has already been cleared. 99 | return; 100 | } 101 | // Notifying the scheduler will hold application stability open until the next tick. 102 | this.scheduler.notify(NotificationSource.PendingTaskRemoved); 103 | this.internalPendingTasks.remove(taskId); 104 | }; 105 | } 106 | 107 | /** 108 | * Runs an asynchronous function and blocks the application's stability until the function completes. 109 | * 110 | * ```ts 111 | * pendingTasks.run(async () => { 112 | * const userData = await fetch('/api/user'); 113 | * this.userData.set(userData); 114 | * }); 115 | * ``` 116 | * 117 | * Application stability is at least delayed until the next tick after the `run` method resolves 118 | * so it is safe to make additional updates to application state that would require UI synchronization: 119 | * 120 | * ```ts 121 | * const userData = await pendingTasks.run(() => fetch('/api/user')); 122 | * this.userData.set(userData); 123 | * ``` 124 | * 125 | * @param fn The asynchronous function to execute 126 | */ 127 | async run(fn: () => Promise): Promise { 128 | const removeTask = this.add(); 129 | try { 130 | return await fn(); 131 | } finally { 132 | removeTask(); 133 | } 134 | } 135 | 136 | /** @nocollapse */ 137 | static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({ 138 | token: PendingTasks, 139 | providedIn: 'root', 140 | factory: () => new PendingTasks(), 141 | }); 142 | } 143 | -------------------------------------------------------------------------------- /src/import/render3/definition_factory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../interface/type'; 10 | 11 | /** 12 | * Definition of what a factory function should look like. 13 | */ 14 | export type FactoryFn = { 15 | /** 16 | * Subclasses without an explicit constructor call through to the factory of their base 17 | * definition, providing it with their own constructor to instantiate. 18 | */ 19 | (t?: Type): U; 20 | 21 | /** 22 | * If no constructor to instantiate is provided, an instance of type T itself is created. 23 | */ 24 | (t?: undefined): T; 25 | }; 26 | 27 | export function getFactoryDef(type: any, throwNotFound: true): FactoryFn; 28 | export function getFactoryDef(type: any): FactoryFn | null; 29 | export function getFactoryDef( 30 | type: any, 31 | throwNotFound?: boolean, 32 | ): FactoryFn | null { 33 | return () => new type(); 34 | } 35 | -------------------------------------------------------------------------------- /src/import/render3/errors_di.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import type { ProviderToken } from '../di'; 10 | import { RuntimeError, RuntimeErrorCode } from '../errors'; 11 | 12 | /** Throws an error when a token is not found in DI. */ 13 | export function throwProviderNotFoundError( 14 | token: ProviderToken, 15 | injectorName?: string, 16 | ): never { 17 | const errorMessage = null; 18 | throw new RuntimeError(RuntimeErrorCode.PROVIDER_NOT_FOUND, errorMessage); 19 | } 20 | -------------------------------------------------------------------------------- /src/import/render3/fields.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { getClosureSafeProperty } from '../util/property'; 10 | export const NG_FACTORY_DEF = getClosureSafeProperty({ 11 | ɵfac: getClosureSafeProperty, 12 | }); 13 | 14 | /** 15 | * The `NG_ENV_ID` field on a DI token indicates special processing in the `EnvironmentInjector`: 16 | * getting such tokens from the `EnvironmentInjector` will bypass the standard DI resolution 17 | * strategy and instead will return implementation produced by the `NG_ENV_ID` factory function. 18 | * 19 | * This particular retrieval of DI tokens is mostly done to eliminate circular dependencies and 20 | * improve tree-shaking. 21 | */ 22 | export const NG_ENV_ID = getClosureSafeProperty({ 23 | __NG_ENV_ID__: getClosureSafeProperty, 24 | }); 25 | -------------------------------------------------------------------------------- /src/import/render3/instructions/di.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * Throws an error indicating that a factory function could not be generated by the compiler for a 11 | * particular class. 12 | * 13 | * This instruction allows the actual error message to be optimized away when ngDevMode is turned 14 | * off, saving bytes of generated code while still providing a good experience in dev mode. 15 | * 16 | * The name of the class is not mentioned here, but will be in the generated factory function name 17 | * and thus in the stack trace. 18 | * 19 | * @codeGenApi 20 | */ 21 | export function ɵɵinvalidFactory(): never { 22 | const msg = 'invalid'; 23 | throw new Error(msg); 24 | } 25 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { SIGNAL } from '@angular/core/primitives/signals'; 10 | 11 | /** 12 | * A reactive value which notifies consumers of any changes. 13 | * 14 | * Signals are functions which returns their current value. To access the current value of a signal, 15 | * call it. 16 | * 17 | * Ordinary values can be turned into `Signal`s with the `signal` function. 18 | */ 19 | export type Signal = (() => T) & { 20 | [SIGNAL]: unknown; 21 | }; 22 | 23 | /** 24 | * Checks if the given `value` is a reactive `Signal`. 25 | */ 26 | export function isSignal(value: unknown): value is Signal { 27 | return ( 28 | typeof value === 'function' && 29 | (value as Signal)[SIGNAL] !== undefined 30 | ); 31 | } 32 | 33 | /** 34 | * A comparison function which can determine if two values are equal. 35 | */ 36 | export type ValueEqualityFn = (a: T, b: T) => boolean; 37 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/asserts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { getActiveConsumer } from '@angular/core/primitives/signals'; 10 | 11 | import { RuntimeError, RuntimeErrorCode } from '../../errors'; 12 | 13 | /** 14 | * Asserts that the current stack frame is not within a reactive context. Useful 15 | * to disallow certain code from running inside a reactive context (see {@link toSignal}). 16 | * 17 | * @param debugFn a reference to the function making the assertion (used for the error message). 18 | * 19 | * @publicApi 20 | */ 21 | export function assertNotInReactiveContext( 22 | debugFn: Function, 23 | extraContext?: string, 24 | ): void { 25 | // Taking a `Function` instead of a string name here prevents the un-minified name of the function 26 | // from being retained in the bundle regardless of minification. 27 | if (getActiveConsumer() !== null) { 28 | throw new RuntimeError( 29 | RuntimeErrorCode.ASSERTION_NOT_INSIDE_REACTIVE_CONTEXT, 30 | null, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { createComputed, SIGNAL } from '@angular/core/primitives/signals'; 10 | 11 | import { Signal, ValueEqualityFn } from './api'; 12 | 13 | /** 14 | * Options passed to the `computed` creation function. 15 | */ 16 | export interface CreateComputedOptions { 17 | /** 18 | * A comparison function which defines equality for computed values. 19 | */ 20 | equal?: ValueEqualityFn; 21 | 22 | /** 23 | * A debug name for the computed signal. Used in Angular DevTools to identify the signal. 24 | */ 25 | debugName?: string; 26 | } 27 | 28 | /** 29 | * Create a computed `Signal` which derives a reactive value from an expression. 30 | */ 31 | export function computed( 32 | computation: () => T, 33 | options?: CreateComputedOptions, 34 | ): Signal { 35 | const getter = createComputed(computation); 36 | if (options?.equal) { 37 | getter[SIGNAL].equal = options.equal; 38 | } 39 | 40 | if (false) { 41 | } 42 | 43 | return getter; 44 | } 45 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { 10 | REACTIVE_NODE, 11 | ReactiveNode, 12 | SIGNAL, 13 | consumerAfterComputation, 14 | consumerBeforeComputation, 15 | consumerDestroy, 16 | consumerPollProducersForChange, 17 | isInNotificationPhase, 18 | } from '@angular/core/primitives/signals'; 19 | import { InjectionToken } from '../../di/injection_token'; 20 | import { inject } from '../../di/injector_compatibility'; 21 | import { Injector } from '../../di/injector'; 22 | import { assertNotInReactiveContext } from './asserts'; 23 | import { assertInInjectionContext } from '../../di/contextual'; 24 | import { DestroyRef } from '../../linker/destroy_ref'; 25 | import { noop } from '../../util/noop'; 26 | import { 27 | ChangeDetectionScheduler, 28 | NotificationSource, 29 | } from '../../change_detection/scheduling/zoneless_scheduling'; 30 | import { EffectScheduler, SchedulableEffect } from './root_effect_scheduler'; 31 | import { USE_MICROTASK_EFFECT_BY_DEFAULT } from './patch'; 32 | import { microtaskEffect } from './microtask_effect'; 33 | 34 | let useMicrotaskEffectsByDefault = USE_MICROTASK_EFFECT_BY_DEFAULT; 35 | 36 | /** 37 | * Toggle the flag on whether to use microtask effects (for testing). 38 | */ 39 | export function setUseMicrotaskEffectsByDefault(value: boolean): boolean { 40 | const prev = useMicrotaskEffectsByDefault; 41 | useMicrotaskEffectsByDefault = value; 42 | return prev; 43 | } 44 | 45 | /** 46 | * A global reactive effect, which can be manually destroyed. 47 | * 48 | * @developerPreview 49 | */ 50 | export interface EffectRef { 51 | /** 52 | * Shut down the effect, removing it from any upcoming scheduled executions. 53 | */ 54 | destroy(): void; 55 | } 56 | 57 | export class EffectRefImpl implements EffectRef { 58 | [SIGNAL]: EffectNode; 59 | 60 | constructor(node: EffectNode) { 61 | this[SIGNAL] = node; 62 | } 63 | 64 | destroy(): void { 65 | this[SIGNAL].destroy(); 66 | } 67 | } 68 | 69 | /** 70 | * Options passed to the `effect` function. 71 | * 72 | * @developerPreview 73 | */ 74 | export interface CreateEffectOptions { 75 | /** 76 | * The `Injector` in which to create the effect. 77 | * 78 | * If this is not provided, the current [injection context](guide/di/dependency-injection-context) 79 | * will be used instead (via `inject`). 80 | */ 81 | injector?: Injector; 82 | 83 | /** 84 | * Whether the `effect` should require manual cleanup. 85 | * 86 | * If this is `false` (the default) the effect will automatically register itself to be cleaned up 87 | * with the current `DestroyRef`. 88 | */ 89 | manualCleanup?: boolean; 90 | 91 | /** 92 | * Always create a root effect (which is scheduled as a microtask) regardless of whether `effect` 93 | * is called within a component. 94 | */ 95 | forceRoot?: true; 96 | 97 | /** 98 | * @deprecated no longer required, signal writes are allowed by default. 99 | */ 100 | allowSignalWrites?: boolean; 101 | 102 | /** 103 | * A debug name for the effect. Used in Angular DevTools to identify the effect. 104 | */ 105 | debugName?: string; 106 | } 107 | 108 | /** 109 | * An effect can, optionally, register a cleanup function. If registered, the cleanup is executed 110 | * before the next effect run. The cleanup function makes it possible to "cancel" any work that the 111 | * previous effect run might have started. 112 | * 113 | * @developerPreview 114 | */ 115 | export type EffectCleanupFn = () => void; 116 | 117 | /** 118 | * A callback passed to the effect function that makes it possible to register cleanup logic. 119 | * 120 | * @developerPreview 121 | */ 122 | export type EffectCleanupRegisterFn = (cleanupFn: EffectCleanupFn) => void; 123 | 124 | /** 125 | * Registers an "effect" that will be scheduled & executed whenever the signals that it reads 126 | * changes. 127 | * 128 | * Angular has two different kinds of effect: component effects and root effects. Component effects 129 | * are created when `effect()` is called from a component, directive, or within a service of a 130 | * component/directive. Root effects are created when `effect()` is called from outside the 131 | * component tree, such as in a root service, or when the `forceRoot` option is provided. 132 | * 133 | * The two effect types differ in their timing. Component effects run as a component lifecycle 134 | * event during Angular's synchronization (change detection) process, and can safely read input 135 | * signals or create/destroy views that depend on component state. Root effects run as microtasks 136 | * and have no connection to the component tree or change detection. 137 | * 138 | * `effect()` must be run in injection context, unless the `injector` option is manually specified. 139 | * 140 | * @developerPreview 141 | */ 142 | export function effect( 143 | effectFn: (onCleanup: EffectCleanupRegisterFn) => void, 144 | options?: CreateEffectOptions, 145 | ): EffectRef { 146 | if (useMicrotaskEffectsByDefault) { 147 | if (ngDevMode && options?.forceRoot) { 148 | throw new Error( 149 | `Cannot use 'forceRoot' option with microtask effects on`, 150 | ); 151 | } 152 | 153 | return microtaskEffect(effectFn, options); 154 | } 155 | 156 | ngDevMode && 157 | assertNotInReactiveContext( 158 | effect, 159 | 'Call `effect` outside of a reactive context. For example, schedule the ' + 160 | 'effect inside the component constructor.', 161 | ); 162 | 163 | !options?.injector && assertInInjectionContext(effect); 164 | 165 | if (ngDevMode && options?.allowSignalWrites !== undefined) { 166 | console.warn( 167 | `The 'allowSignalWrites' flag is deprecated and no longer impacts effect() (writes are always allowed)`, 168 | ); 169 | } 170 | 171 | const injector = options?.injector ?? inject(Injector); 172 | const destroyRef = 173 | options?.manualCleanup !== true ? injector.get(DestroyRef) : null; 174 | 175 | let node: EffectNode; 176 | 177 | const notifier = injector.get(ChangeDetectionScheduler); 178 | node = createRootEffect(effectFn, injector.get(EffectScheduler), notifier); 179 | node.injector = injector; 180 | 181 | if (destroyRef !== null) { 182 | // If we need to register for cleanup, do that here. 183 | node.onDestroyFn = destroyRef.onDestroy(() => node.destroy()); 184 | } 185 | 186 | const effectRef = new EffectRefImpl(node); 187 | 188 | return effectRef; 189 | } 190 | 191 | export interface EffectNode extends ReactiveNode, SchedulableEffect { 192 | hasRun: boolean; 193 | cleanupFns: EffectCleanupFn[] | undefined; 194 | injector: Injector; 195 | notifier: ChangeDetectionScheduler; 196 | 197 | onDestroyFn: () => void; 198 | fn: (cleanupFn: EffectCleanupRegisterFn) => void; 199 | run(): void; 200 | destroy(): void; 201 | maybeCleanup(): void; 202 | } 203 | 204 | export interface RootEffectNode extends EffectNode { 205 | scheduler: EffectScheduler; 206 | } 207 | 208 | /** 209 | * Not public API, which guarantees `EffectScheduler` only ever comes from the application root 210 | * injector. 211 | */ 212 | export const APP_EFFECT_SCHEDULER = /* @__PURE__ */ new InjectionToken('', { 213 | providedIn: 'root', 214 | factory: () => inject(EffectScheduler), 215 | }); 216 | 217 | export const BASE_EFFECT_NODE: Omit< 218 | EffectNode, 219 | 'fn' | 'destroy' | 'injector' | 'notifier' 220 | > = /* @__PURE__ */ (() => ({ 221 | ...REACTIVE_NODE, 222 | consumerIsAlwaysLive: true, 223 | consumerAllowSignalWrites: true, 224 | dirty: true, 225 | hasRun: false, 226 | cleanupFns: undefined, 227 | zone: null, 228 | kind: 'effect', 229 | onDestroyFn: noop, 230 | run(this: EffectNode): void { 231 | this.dirty = false; 232 | 233 | if (ngDevMode && isInNotificationPhase()) { 234 | throw new Error( 235 | `Schedulers cannot synchronously execute watches while scheduling.`, 236 | ); 237 | } 238 | 239 | if (this.hasRun && !consumerPollProducersForChange(this)) { 240 | return; 241 | } 242 | this.hasRun = true; 243 | 244 | const registerCleanupFn: EffectCleanupRegisterFn = (cleanupFn) => 245 | (this.cleanupFns ??= []).push(cleanupFn); 246 | 247 | const prevNode = consumerBeforeComputation(this); 248 | 249 | // We clear `setIsRefreshingViews` so that `markForCheck()` within the body of an effect will 250 | // cause CD to reach the component in question. 251 | 252 | try { 253 | this.maybeCleanup(); 254 | this.fn(registerCleanupFn); 255 | } finally { 256 | consumerAfterComputation(this, prevNode); 257 | } 258 | }, 259 | 260 | maybeCleanup(this: EffectNode): void { 261 | if (!this.cleanupFns?.length) { 262 | return; 263 | } 264 | try { 265 | // Attempt to run the cleanup functions. Regardless of failure or success, we consider 266 | // cleanup "completed" and clear the list for the next run of the effect. Note that an error 267 | // from the cleanup function will still crash the current run of the effect. 268 | while (this.cleanupFns.length) { 269 | this.cleanupFns.pop()!(); 270 | } 271 | } finally { 272 | this.cleanupFns = []; 273 | } 274 | }, 275 | }))(); 276 | 277 | export const ROOT_EFFECT_NODE: Omit< 278 | RootEffectNode, 279 | 'fn' | 'scheduler' | 'notifier' | 'injector' 280 | > = /* @__PURE__ */ (() => ({ 281 | ...BASE_EFFECT_NODE, 282 | consumerMarkedDirty(this: RootEffectNode) { 283 | this.scheduler.schedule(this); 284 | this.notifier.notify(NotificationSource.RootEffect); 285 | }, 286 | destroy(this: RootEffectNode) { 287 | consumerDestroy(this); 288 | this.onDestroyFn(); 289 | this.maybeCleanup(); 290 | this.scheduler.remove(this); 291 | }, 292 | }))(); 293 | 294 | export function createRootEffect( 295 | fn: (onCleanup: EffectCleanupRegisterFn) => void, 296 | scheduler: EffectScheduler, 297 | notifier: ChangeDetectionScheduler, 298 | ): RootEffectNode { 299 | const node = Object.create(ROOT_EFFECT_NODE) as RootEffectNode; 300 | node.fn = fn; 301 | node.scheduler = scheduler; 302 | node.notifier = notifier; 303 | node.zone = typeof Zone !== 'undefined' ? Zone.current : null; 304 | node.scheduler.schedule(node); 305 | node.notifier.notify(NotificationSource.RootEffect); 306 | return node; 307 | } 308 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/linked_signal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { signalAsReadonlyFn, WritableSignal } from './signal'; 10 | import { Signal, ValueEqualityFn } from './api'; 11 | import { 12 | ComputationFn, 13 | createLinkedSignal, 14 | LinkedSignalGetter, 15 | LinkedSignalNode, 16 | SIGNAL, 17 | linkedSignalSetFn, 18 | linkedSignalUpdateFn, 19 | } from '@angular/core/primitives/signals'; 20 | 21 | const identityFn = (v: T) => v; 22 | 23 | /** 24 | * Creates a writable signal whose value is initialized and reset by the linked, reactive computation. 25 | * 26 | * @developerPreview 27 | */ 28 | export function linkedSignal( 29 | computation: () => D, 30 | options?: { equal?: ValueEqualityFn> }, 31 | ): WritableSignal; 32 | 33 | /** 34 | * Creates a writable signal whose value is initialized and reset by the linked, reactive computation. 35 | * This is an advanced API form where the computation has access to the previous value of the signal and the computation result. 36 | * 37 | * Note: The computation is reactive, meaning the linked signal will automatically update whenever any of the signals used within the computation change. 38 | * 39 | * @developerPreview 40 | */ 41 | export function linkedSignal(options: { 42 | source: () => S; 43 | computation: ( 44 | source: NoInfer, 45 | previous?: { source: NoInfer; value: NoInfer }, 46 | ) => D; 47 | equal?: ValueEqualityFn>; 48 | }): WritableSignal; 49 | 50 | export function linkedSignal( 51 | optionsOrComputation: 52 | | { 53 | source: () => S; 54 | computation: ComputationFn; 55 | equal?: ValueEqualityFn; 56 | } 57 | | (() => D), 58 | options?: { equal?: ValueEqualityFn }, 59 | ): WritableSignal { 60 | if (typeof optionsOrComputation === 'function') { 61 | const getter = createLinkedSignal( 62 | optionsOrComputation, 63 | identityFn, 64 | options?.equal, 65 | ) as LinkedSignalGetter & WritableSignal; 66 | return upgradeLinkedSignalGetter(getter); 67 | } else { 68 | const getter = createLinkedSignal( 69 | optionsOrComputation.source, 70 | optionsOrComputation.computation, 71 | optionsOrComputation.equal, 72 | ); 73 | return upgradeLinkedSignalGetter(getter); 74 | } 75 | } 76 | 77 | function upgradeLinkedSignalGetter( 78 | getter: LinkedSignalGetter, 79 | ): WritableSignal { 80 | if (false) { 81 | } 82 | 83 | const node = getter[SIGNAL] as LinkedSignalNode; 84 | const upgradedGetter = getter as LinkedSignalGetter & WritableSignal; 85 | 86 | upgradedGetter.set = (newValue: D) => linkedSignalSetFn(node, newValue); 87 | upgradedGetter.update = (updateFn: (value: D) => D) => 88 | linkedSignalUpdateFn(node, updateFn); 89 | upgradedGetter.asReadonly = signalAsReadonlyFn.bind( 90 | getter as any, 91 | ) as () => Signal; 92 | 93 | return upgradedGetter; 94 | } 95 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/microtask_effect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { 10 | createWatch, 11 | Watch, 12 | WatchCleanupRegisterFn, 13 | } from '@angular/core/primitives/signals'; 14 | 15 | import { Injector } from '../../di/injector'; 16 | import { inject } from '../../di/injector_compatibility'; 17 | import { ɵɵdefineInjectable } from '../../di/interface/defs'; 18 | import { ErrorHandler } from '../../error_handler'; 19 | import { DestroyRef } from '../../linker/destroy_ref'; 20 | 21 | import type { 22 | CreateEffectOptions, 23 | EffectCleanupRegisterFn, 24 | EffectRef, 25 | } from './effect'; 26 | import { 27 | type SchedulableEffect, 28 | ZoneAwareEffectScheduler, 29 | } from './root_effect_scheduler'; 30 | import { assertInInjectionContext } from '../../di'; 31 | import { PendingTasksInternal } from '../../pending_tasks'; 32 | 33 | export class MicrotaskEffectScheduler extends ZoneAwareEffectScheduler { 34 | private readonly pendingTasks = inject(PendingTasksInternal); 35 | private taskId: number | null = null; 36 | 37 | override schedule(effect: SchedulableEffect): void { 38 | // Check whether there are any pending effects _before_ queueing in the base class. 39 | super.schedule(effect); 40 | if (this.taskId === null) { 41 | this.taskId = this.pendingTasks.add(); 42 | queueMicrotask(() => this.flush()); 43 | } 44 | } 45 | 46 | override flush(): void { 47 | try { 48 | super.flush(); 49 | } finally { 50 | if (this.taskId !== null) { 51 | this.pendingTasks.remove(this.taskId); 52 | this.taskId = null; 53 | } 54 | } 55 | } 56 | 57 | /** @nocollapse */ 58 | static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({ 59 | token: MicrotaskEffectScheduler, 60 | providedIn: 'root', 61 | factory: () => new MicrotaskEffectScheduler(), 62 | }); 63 | } 64 | 65 | /** 66 | * Core reactive node for an Angular effect. 67 | * 68 | * `EffectHandle` combines the reactive graph's `Watch` base node for effects with the framework's 69 | * scheduling abstraction (`MicrotaskEffectScheduler`) as well as automatic cleanup via `DestroyRef` 70 | * if available/requested. 71 | */ 72 | class EffectHandle implements EffectRef, SchedulableEffect { 73 | unregisterOnDestroy: (() => void) | undefined; 74 | readonly watcher: Watch; 75 | 76 | constructor( 77 | private scheduler: MicrotaskEffectScheduler, 78 | private effectFn: (onCleanup: EffectCleanupRegisterFn) => void, 79 | public zone: Zone | null, 80 | destroyRef: DestroyRef | null, 81 | private injector: Injector, 82 | allowSignalWrites: boolean, 83 | ) { 84 | this.watcher = createWatch( 85 | (onCleanup) => this.runEffect(onCleanup), 86 | () => this.schedule(), 87 | allowSignalWrites, 88 | ); 89 | this.unregisterOnDestroy = destroyRef?.onDestroy(() => this.destroy()); 90 | } 91 | 92 | private runEffect(onCleanup: WatchCleanupRegisterFn): void { 93 | try { 94 | this.effectFn(onCleanup); 95 | } catch (err) { 96 | // Inject the `ErrorHandler` here in order to avoid circular DI error 97 | // if the effect is used inside of a custom `ErrorHandler`. 98 | const errorHandler = this.injector.get(ErrorHandler, null, { 99 | optional: true, 100 | }); 101 | errorHandler?.handleError(err); 102 | } 103 | } 104 | 105 | run(): void { 106 | this.watcher.run(); 107 | } 108 | 109 | private schedule(): void { 110 | this.scheduler.schedule(this); 111 | } 112 | 113 | destroy(): void { 114 | this.watcher.destroy(); 115 | this.unregisterOnDestroy?.(); 116 | 117 | // Note: if the effect is currently scheduled, it's not un-scheduled, and so the scheduler will 118 | // retain a reference to it. Attempting to execute it will be a no-op. 119 | } 120 | } 121 | 122 | // Just used for the name for the debug error below. 123 | function effect() {} 124 | 125 | /** 126 | * Create a global `Effect` for the given reactive function. 127 | */ 128 | export function microtaskEffect( 129 | effectFn: (onCleanup: EffectCleanupRegisterFn) => void, 130 | options?: CreateEffectOptions, 131 | ): EffectRef { 132 | !options?.injector && assertInInjectionContext(effect); 133 | 134 | const injector = options?.injector ?? inject(Injector); 135 | const destroyRef = 136 | options?.manualCleanup !== true ? injector.get(DestroyRef) : null; 137 | 138 | const handle = new EffectHandle( 139 | injector.get(MicrotaskEffectScheduler), 140 | effectFn, 141 | typeof Zone === 'undefined' ? null : Zone.current, 142 | destroyRef, 143 | injector, 144 | options?.allowSignalWrites ?? false, 145 | ); 146 | 147 | // Effects need to be marked dirty manually to trigger their initial run. The timing of this 148 | // marking matters, because the effects may read signals that track component inputs, which are 149 | // only available after those components have had their first update pass. 150 | // 151 | // We inject `ChangeDetectorRef` optionally, to determine whether this effect is being created in 152 | // the context of a component or not. If it is, then we check whether the component has already 153 | // run its update pass, and defer the effect's initial scheduling until the update pass if it 154 | // hasn't already run. 155 | handle.watcher.notify(); 156 | 157 | return handle; 158 | } 159 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/patch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * Controls whether effects use the legacy `microtaskEffect` by default. 11 | */ 12 | export const USE_MICROTASK_EFFECT_BY_DEFAULT = false; 13 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/root_effect_scheduler.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { ɵɵdefineInjectable } from '../../di/interface/defs'; 10 | 11 | /** 12 | * Abstraction that encompasses any kind of effect that can be scheduled. 13 | */ 14 | export interface SchedulableEffect { 15 | run(): void; 16 | zone: { 17 | run(fn: () => T): T; 18 | } | null; 19 | } 20 | 21 | /** 22 | * A scheduler which manages the execution of effects. 23 | */ 24 | export abstract class EffectScheduler { 25 | /** 26 | * Schedule the given effect to be executed at a later time. 27 | * 28 | * It is an error to attempt to execute any effects synchronously during a scheduling operation. 29 | */ 30 | abstract schedule(e: SchedulableEffect): void; 31 | 32 | /** 33 | * Run any scheduled effects. 34 | */ 35 | abstract flush(): void; 36 | 37 | /** Remove a scheduled effect */ 38 | abstract remove(e: SchedulableEffect): void; 39 | 40 | /** @nocollapse */ 41 | static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({ 42 | token: EffectScheduler, 43 | providedIn: 'root', 44 | factory: () => new ZoneAwareEffectScheduler(), 45 | }); 46 | } 47 | 48 | /** 49 | * A wrapper around `ZoneAwareQueueingScheduler` that schedules flushing via the microtask queue 50 | * when. 51 | */ 52 | export class ZoneAwareEffectScheduler implements EffectScheduler { 53 | private queuedEffectCount = 0; 54 | private queues = new Map>(); 55 | 56 | schedule(handle: SchedulableEffect): void { 57 | this.enqueue(handle); 58 | } 59 | 60 | remove(handle: SchedulableEffect): void { 61 | const zone = handle.zone as Zone | null; 62 | const queue = this.queues.get(zone)!; 63 | if (!queue.has(handle)) { 64 | return; 65 | } 66 | 67 | queue.delete(handle); 68 | this.queuedEffectCount--; 69 | } 70 | 71 | private enqueue(handle: SchedulableEffect): void { 72 | const zone = handle.zone as Zone | null; 73 | if (!this.queues.has(zone)) { 74 | this.queues.set(zone, new Set()); 75 | } 76 | 77 | const queue = this.queues.get(zone)!; 78 | if (queue.has(handle)) { 79 | return; 80 | } 81 | this.queuedEffectCount++; 82 | queue.add(handle); 83 | } 84 | 85 | /** 86 | * Run all scheduled effects. 87 | * 88 | * Execution order of effects within the same zone is guaranteed to be FIFO, but there is no 89 | * ordering guarantee between effects scheduled in different zones. 90 | */ 91 | flush(): void { 92 | while (this.queuedEffectCount > 0) { 93 | for (const [zone, queue] of this.queues) { 94 | // `zone` here must be defined. 95 | if (zone === null) { 96 | this.flushQueue(queue); 97 | } else { 98 | zone.run(() => this.flushQueue(queue)); 99 | } 100 | } 101 | } 102 | } 103 | 104 | private flushQueue(queue: Set): void { 105 | for (const handle of queue) { 106 | queue.delete(handle); 107 | this.queuedEffectCount--; 108 | 109 | // TODO: what happens if this throws an error? 110 | handle.run(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/signal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { 10 | createSignal, 11 | SIGNAL, 12 | SignalGetter, 13 | SignalNode, 14 | signalSetFn, 15 | signalUpdateFn, 16 | } from '@angular/core/primitives/signals'; 17 | 18 | import { isSignal, Signal, ValueEqualityFn } from './api'; 19 | 20 | /** Symbol used distinguish `WritableSignal` from other non-writable signals and functions. */ 21 | export const ɵWRITABLE_SIGNAL = /* @__PURE__ */ Symbol('WRITABLE_SIGNAL'); 22 | 23 | /** 24 | * A `Signal` with a value that can be mutated via a setter interface. 25 | */ 26 | export interface WritableSignal extends Signal { 27 | [ɵWRITABLE_SIGNAL]: T; 28 | 29 | /** 30 | * Directly set the signal to a new value, and notify any dependents. 31 | */ 32 | set(value: T): void; 33 | 34 | /** 35 | * Update the value of the signal based on its current value, and 36 | * notify any dependents. 37 | */ 38 | update(updateFn: (value: T) => T): void; 39 | 40 | /** 41 | * Returns a readonly version of this signal. Readonly signals can be accessed to read their value 42 | * but can't be changed using set or update methods. The readonly signals do _not_ have 43 | * any built-in mechanism that would prevent deep-mutation of their value. 44 | */ 45 | asReadonly(): Signal; 46 | } 47 | 48 | /** 49 | * Utility function used during template type checking to extract the value from a `WritableSignal`. 50 | * @codeGenApi 51 | */ 52 | export function ɵunwrapWritableSignal( 53 | value: T | { [ɵWRITABLE_SIGNAL]: T }, 54 | ): T { 55 | // Note: the function uses `WRITABLE_SIGNAL` as a brand instead of `WritableSignal`, 56 | // because the latter incorrectly unwraps non-signal getter functions. 57 | return null!; 58 | } 59 | 60 | /** 61 | * Options passed to the `signal` creation function. 62 | */ 63 | export interface CreateSignalOptions { 64 | /** 65 | * A comparison function which defines equality for signal values. 66 | */ 67 | equal?: ValueEqualityFn; 68 | 69 | /** 70 | * A debug name for the signal. Used in Angular DevTools to identify the signal. 71 | */ 72 | debugName?: string; 73 | } 74 | 75 | /** 76 | * Create a `Signal` that can be set or updated directly. 77 | */ 78 | export function signal( 79 | initialValue: T, 80 | options?: CreateSignalOptions, 81 | ): WritableSignal { 82 | const signalFn = createSignal(initialValue) as SignalGetter & 83 | WritableSignal; 84 | const node = signalFn[SIGNAL]; 85 | if (options?.equal) { 86 | node.equal = options.equal; 87 | } 88 | 89 | signalFn.set = (newValue: T) => signalSetFn(node, newValue); 90 | signalFn.update = (updateFn: (value: T) => T) => 91 | signalUpdateFn(node, updateFn); 92 | signalFn.asReadonly = signalAsReadonlyFn.bind( 93 | signalFn as any, 94 | ) as () => Signal; 95 | 96 | if (false) { 97 | } 98 | 99 | return signalFn as WritableSignal; 100 | } 101 | 102 | export function signalAsReadonlyFn(this: SignalGetter): Signal { 103 | const node = this[SIGNAL] as SignalNode & { readonlyFn?: Signal }; 104 | if (node.readonlyFn === undefined) { 105 | const readonlyFn = () => this(); 106 | (readonlyFn as any)[SIGNAL] = node; 107 | node.readonlyFn = readonlyFn as Signal; 108 | } 109 | return node.readonlyFn; 110 | } 111 | 112 | /** 113 | * Checks if the given `value` is a writeable signal. 114 | */ 115 | export function isWritableSignal( 116 | value: unknown, 117 | ): value is WritableSignal { 118 | return isSignal(value) && typeof (value as any).set === 'function'; 119 | } 120 | -------------------------------------------------------------------------------- /src/import/render3/reactivity/untracked.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { setActiveConsumer } from '@angular/core/primitives/signals'; 10 | 11 | /** 12 | * Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function 13 | * can, optionally, return a value. 14 | */ 15 | export function untracked(nonReactiveReadsFn: () => T): T { 16 | const prevConsumer = setActiveConsumer(null); 17 | // We are not trying to catch any particular errors here, just making sure that the consumers 18 | // stack is restored in case of errors. 19 | try { 20 | return nonReactiveReadsFn(); 21 | } finally { 22 | setActiveConsumer(prevConsumer); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/import/render3/util/stringify_utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * Used for stringify render output in Ivy. 11 | * Important! This function is very performance-sensitive and we should 12 | * be extra careful not to introduce megamorphic reads in it. 13 | * Check `core/test/render3/perf/render_stringify` for benchmarks and alternate implementations. 14 | */ 15 | export function renderStringify(value: any): string { 16 | if (typeof value === 'string') return value; 17 | if (value == null) return ''; 18 | // Use `String` so that it invokes the `toString` method of the value. Note that this 19 | // appears to be faster than calling `value.toString` (see `render_stringify` benchmark). 20 | return String(value); 21 | } 22 | 23 | /** 24 | * Used to stringify a value so that it can be displayed in an error message. 25 | * 26 | * Important! This function contains a megamorphic read and should only be 27 | * used for error messages. 28 | */ 29 | export function stringifyForError(value: any): string { 30 | if (typeof value === 'function') return value.name || value.toString(); 31 | if ( 32 | typeof value === 'object' && 33 | value != null && 34 | typeof value.type === 'function' 35 | ) { 36 | return value.type.name || value.type.toString(); 37 | } 38 | 39 | return renderStringify(value); 40 | } 41 | -------------------------------------------------------------------------------- /src/import/resource/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Injector } from '../di/injector'; 10 | import { Signal, ValueEqualityFn } from '../render3/reactivity/api'; 11 | import { WritableSignal } from '../render3/reactivity/signal'; 12 | 13 | /** 14 | * Status of a `Resource`. 15 | * 16 | * @experimental 17 | */ 18 | export enum ResourceStatus { 19 | /** 20 | * The resource has no valid request and will not perform any loading. 21 | * 22 | * `value()` will be `undefined`. 23 | */ 24 | Idle, 25 | 26 | /** 27 | * Loading failed with an error. 28 | * 29 | * `value()` will be `undefined`. 30 | */ 31 | Error, 32 | 33 | /** 34 | * The resource is currently loading a new value as a result of a change in its `request`. 35 | * 36 | * `value()` will be `undefined`. 37 | */ 38 | Loading, 39 | 40 | /** 41 | * The resource is currently reloading a fresh value for the same request. 42 | * 43 | * `value()` will continue to return the previously fetched value during the reloading operation. 44 | */ 45 | Reloading, 46 | 47 | /** 48 | * Loading has completed and the resource has the value returned from the loader. 49 | */ 50 | Resolved, 51 | 52 | /** 53 | * The resource's value was set locally via `.set()` or `.update()`. 54 | */ 55 | Local, 56 | } 57 | 58 | /** 59 | * A Resource is an asynchronous dependency (for example, the results of an API call) that is 60 | * managed and delivered through signals. 61 | * 62 | * The usual way of creating a `Resource` is through the `resource` function, but various other APIs 63 | * may present `Resource` instances to describe their own concepts. 64 | * 65 | * @experimental 66 | */ 67 | export interface Resource { 68 | /** 69 | * The current value of the `Resource`, or `undefined` if there is no current value. 70 | */ 71 | readonly value: Signal; 72 | 73 | /** 74 | * The current status of the `Resource`, which describes what the resource is currently doing and 75 | * what can be expected of its `value`. 76 | */ 77 | readonly status: Signal; 78 | 79 | /** 80 | * When in the `error` state, this returns the last known error from the `Resource`. 81 | */ 82 | readonly error: Signal; 83 | 84 | /** 85 | * Whether this resource is loading a new value (or reloading the existing one). 86 | */ 87 | readonly isLoading: Signal; 88 | 89 | /** 90 | * Whether this resource has a valid current value. 91 | * 92 | * This function is reactive. 93 | */ 94 | hasValue(): this is Resource>; 95 | 96 | /** 97 | * Instructs the resource to re-load any asynchronous dependency it may have. 98 | * 99 | * Note that the resource will not enter its reloading state until the actual backend request is 100 | * made. 101 | * 102 | * @returns true if a reload was initiated, false if a reload was unnecessary or unsupported 103 | */ 104 | reload(): boolean; 105 | } 106 | 107 | /** 108 | * A `Resource` with a mutable value. 109 | * 110 | * Overwriting the value of a resource sets it to the 'local' state. 111 | * 112 | * @experimental 113 | */ 114 | export interface WritableResource extends Resource { 115 | readonly value: WritableSignal; 116 | hasValue(): this is WritableResource>; 117 | 118 | /** 119 | * Convenience wrapper for `value.set`. 120 | */ 121 | set(value: T): void; 122 | 123 | /** 124 | * Convenience wrapper for `value.update`. 125 | */ 126 | update(updater: (value: T) => T): void; 127 | asReadonly(): Resource; 128 | } 129 | 130 | /** 131 | * A `WritableResource` created through the `resource` function. 132 | * 133 | * @experimental 134 | */ 135 | export interface ResourceRef extends WritableResource { 136 | hasValue(): this is ResourceRef>; 137 | 138 | /** 139 | * Manually destroy the resource, which cancels pending requests and returns it to `idle` state. 140 | */ 141 | destroy(): void; 142 | } 143 | 144 | /** 145 | * Parameter to a `ResourceLoader` which gives the request and other options for the current loading 146 | * operation. 147 | * 148 | * @experimental 149 | */ 150 | export interface ResourceLoaderParams { 151 | request: Exclude, undefined>; 152 | abortSignal: AbortSignal; 153 | previous: { 154 | status: ResourceStatus; 155 | }; 156 | } 157 | 158 | /** 159 | * Loading function for a `Resource`. 160 | * 161 | * @experimental 162 | */ 163 | export type ResourceLoader = ( 164 | param: ResourceLoaderParams, 165 | ) => PromiseLike; 166 | 167 | /** 168 | * Streaming loader for a `Resource`. 169 | * 170 | * @experimental 171 | */ 172 | export type ResourceStreamingLoader = ( 173 | param: ResourceLoaderParams, 174 | ) => PromiseLike>>; 175 | 176 | /** 177 | * Options to the `resource` function, for creating a resource. 178 | * 179 | * @experimental 180 | */ 181 | export interface BaseResourceOptions { 182 | /** 183 | * A reactive function which determines the request to be made. Whenever the request changes, the 184 | * loader will be triggered to fetch a new value for the resource. 185 | * 186 | * If a request function isn't provided, the loader won't rerun unless the resource is reloaded. 187 | */ 188 | request?: () => R; 189 | 190 | /** 191 | * The value which will be returned from the resource when a server value is unavailable, such as 192 | * when the resource is still loading, or in an error state. 193 | */ 194 | defaultValue?: NoInfer; 195 | 196 | /** 197 | * Equality function used to compare the return value of the loader. 198 | */ 199 | equal?: ValueEqualityFn; 200 | 201 | /** 202 | * Overrides the `Injector` used by `resource`. 203 | */ 204 | injector?: Injector; 205 | } 206 | 207 | /** 208 | * Options to the `resource` function, for creating a resource. 209 | * 210 | * @experimental 211 | */ 212 | export interface PromiseResourceOptions 213 | extends BaseResourceOptions { 214 | /** 215 | * Loading function which returns a `Promise` of the resource's value for a given request. 216 | */ 217 | loader: ResourceLoader; 218 | 219 | /** 220 | * Cannot specify `stream` and `loader` at the same time. 221 | */ 222 | stream?: never; 223 | } 224 | 225 | /** 226 | * Options to the `resource` function, for creating a resource. 227 | * 228 | * @experimental 229 | */ 230 | export interface StreamingResourceOptions 231 | extends BaseResourceOptions { 232 | /** 233 | * Loading function which returns a `Promise` of a signal of the resource's value for a given 234 | * request, which can change over time as new values are received from a stream. 235 | */ 236 | stream: ResourceStreamingLoader; 237 | 238 | /** 239 | * Cannot specify `stream` and `loader` at the same time. 240 | */ 241 | loader?: never; 242 | } 243 | 244 | /** 245 | * @experimental 246 | */ 247 | export type ResourceOptions = 248 | | PromiseResourceOptions 249 | | StreamingResourceOptions; 250 | 251 | /** 252 | * @experimental 253 | */ 254 | export type ResourceStreamItem = { value: T } | { error: unknown }; 255 | -------------------------------------------------------------------------------- /src/import/resource/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | export * from './api'; 10 | export { resource } from './resource'; 11 | -------------------------------------------------------------------------------- /src/import/util/callback_scheduler.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { noop } from './noop'; 10 | 11 | /** 12 | * Gets a scheduling function that runs the callback after the first of setTimeout and 13 | * requestAnimationFrame resolves. 14 | * 15 | * - `requestAnimationFrame` ensures that change detection runs ahead of a browser repaint. 16 | * This ensures that the create and update passes of a change detection always happen 17 | * in the same frame. 18 | * - When the browser is resource-starved, `rAF` can execute _before_ a `setTimeout` because 19 | * rendering is a very high priority process. This means that `setTimeout` cannot guarantee 20 | * same-frame create and update pass, when `setTimeout` is used to schedule the update phase. 21 | * - While `rAF` gives us the desirable same-frame updates, it has two limitations that 22 | * prevent it from being used alone. First, it does not run in background tabs, which would 23 | * prevent Angular from initializing an application when opened in a new tab (for example). 24 | * Second, repeated calls to requestAnimationFrame will execute at the refresh rate of the 25 | * hardware (~16ms for a 60Hz display). This would cause significant slowdown of tests that 26 | * are written with several updates and asserts in the form of "update; await stable; assert;". 27 | * - Both `setTimeout` and `rAF` are able to "coalesce" several events from a single user 28 | * interaction into a single change detection. Importantly, this reduces view tree traversals when 29 | * compared to an alternative timing mechanism like `queueMicrotask`, where change detection would 30 | * then be interleaves between each event. 31 | * 32 | * By running change detection after the first of `setTimeout` and `rAF` to execute, we get the 33 | * best of both worlds. 34 | * 35 | * @returns a function to cancel the scheduled callback 36 | */ 37 | export function scheduleCallbackWithRafRace(callback: Function): () => void { 38 | let timeoutId: number; 39 | let animationFrameId: number; 40 | function cleanup() { 41 | callback = noop; 42 | try { 43 | if ( 44 | animationFrameId !== undefined && 45 | typeof cancelAnimationFrame === 'function' 46 | ) { 47 | cancelAnimationFrame(animationFrameId); 48 | } 49 | if (timeoutId !== undefined) { 50 | clearTimeout(timeoutId); 51 | } 52 | } catch { 53 | // Clearing/canceling can fail in tests due to the timing of functions being patched and unpatched 54 | // Just ignore the errors - we protect ourselves from this issue by also making the callback a no-op. 55 | } 56 | } 57 | timeoutId = setTimeout(() => { 58 | callback(); 59 | cleanup(); 60 | }) as unknown as number; 61 | if (typeof requestAnimationFrame === 'function') { 62 | animationFrameId = requestAnimationFrame(() => { 63 | callback(); 64 | cleanup(); 65 | }); 66 | } 67 | 68 | return () => cleanup(); 69 | } 70 | 71 | export function scheduleCallbackWithMicrotask(callback: Function): () => void { 72 | queueMicrotask(() => callback()); 73 | 74 | return () => { 75 | callback = noop; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /src/import/util/closure.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * Convince closure compiler that the wrapped function has no side-effects. 11 | * 12 | * Closure compiler always assumes that `toString` has no side-effects. We use this quirk to 13 | * allow us to execute a function but have closure compiler mark the call as no-side-effects. 14 | * It is important that the return value for the `noSideEffects` function be assigned 15 | * to something which is retained otherwise the call to `noSideEffects` will be removed by closure 16 | * compiler. 17 | */ 18 | export function noSideEffects(fn: () => T): T { 19 | return { toString: fn }.toString() as unknown as T; 20 | } 21 | -------------------------------------------------------------------------------- /src/import/util/decorators.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { Type } from '../interface/type'; 10 | 11 | import { noSideEffects } from './closure'; 12 | 13 | /** 14 | * An interface implemented by all Angular type decorators, which allows them to be used as 15 | * decorators as well as Angular syntax. 16 | * 17 | * ```ts 18 | * @ng.Component({...}) 19 | * class MyClass {...} 20 | * ``` 21 | * 22 | * @publicApi 23 | */ 24 | export interface TypeDecorator { 25 | /** 26 | * Invoke as decorator. 27 | */ 28 | >(type: T): T; 29 | 30 | // Make TypeDecorator assignable to built-in ParameterDecorator type. 31 | // ParameterDecorator is declared in lib.d.ts as a `declare type` 32 | // so we cannot declare this interface as a subtype. 33 | // see https://github.com/angular/angular/issues/3379#issuecomment-126169417 34 | ( 35 | target: object, 36 | propertyKey?: string | symbol, 37 | parameterIndex?: number, 38 | ): void; 39 | // Standard (non-experimental) Decorator signature that avoids direct usage of 40 | // any TS 5.0+ specific types. 41 | (target: unknown, context: unknown): void; 42 | } 43 | export const PARAMETERS = '__parameters__'; 44 | 45 | function makeMetadataCtor(props?: (...args: any[]) => any): any { 46 | return function ctor(this: any, ...args: any[]) { 47 | if (props) { 48 | const values = props(...args); 49 | for (const propName in values) { 50 | this[propName] = values[propName]; 51 | } 52 | } 53 | }; 54 | } 55 | 56 | export function makeParamDecorator( 57 | name: string, 58 | props?: (...args: any[]) => any, 59 | parentClass?: any, 60 | ): any { 61 | return noSideEffects(() => { 62 | const metaCtor = makeMetadataCtor(props); 63 | function ParamDecoratorFactory( 64 | this: unknown | typeof ParamDecoratorFactory, 65 | ...args: any[] 66 | ): any { 67 | metaCtor.apply(this, args); 68 | return this; 69 | } 70 | if (parentClass) { 71 | } 72 | 73 | return ParamDecoratorFactory; 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /src/import/util/empty.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * This file contains reuseable "empty" symbols that can be used as default return values 11 | * in different parts of the rendering code. Because the same symbols are returned, this 12 | * allows for identity checks against these values to be consistently used by the framework 13 | * code. 14 | */ 15 | 16 | export const EMPTY_OBJ: never = {} as never; 17 | export const EMPTY_ARRAY: any[] = []; 18 | 19 | // freezing the values prevents any code from accidentally inserting new values in 20 | if (false) { 21 | } 22 | -------------------------------------------------------------------------------- /src/import/util/noop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | export function noop(...args: any[]): any { 10 | // Do nothing. 11 | } 12 | -------------------------------------------------------------------------------- /src/import/util/property.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | export function getClosureSafeProperty(objWithPropertyToExtract: T): string { 10 | for (const key in objWithPropertyToExtract) { 11 | if (objWithPropertyToExtract[key] === (getClosureSafeProperty as any)) { 12 | return key; 13 | } 14 | } 15 | throw Error('Could not find renamed property on target object.'); 16 | } 17 | -------------------------------------------------------------------------------- /src/import/util/stringify.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | export function stringify(token: any): string { 10 | if (typeof token === 'string') { 11 | return token; 12 | } 13 | 14 | if (Array.isArray(token)) { 15 | return `[${token.map(stringify).join(', ')}]`; 16 | } 17 | 18 | if (token == null) { 19 | return '' + token; 20 | } 21 | 22 | const name = token.overriddenName || token.name; 23 | if (name) { 24 | return `${name}`; 25 | } 26 | 27 | const result = token.toString(); 28 | 29 | if (result == null) { 30 | return '' + result; 31 | } 32 | 33 | const newLineIndex = result.indexOf('\n'); 34 | return newLineIndex >= 0 ? result.slice(0, newLineIndex) : result; 35 | } 36 | 37 | /** 38 | * Ellipses the string in the middle when longer than the max length 39 | * 40 | * @param string 41 | * @param maxLength of the output string 42 | * @returns ellipsed string with ... in the middle 43 | */ 44 | export function truncateMiddle(str: string, maxLength = 100): string { 45 | if (!str || maxLength < 1 || str.length <= maxLength) return str; 46 | if (maxLength == 1) return str.substring(0, 1) + '...'; 47 | 48 | const halfLimit = Math.round(maxLength / 2); 49 | return ( 50 | str.substring(0, halfLimit) + '...' + str.substring(str.length - halfLimit) 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/primitives/signals/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | export { ComputedNode, createComputed } from './src/computed'; 10 | export { 11 | ComputationFn, 12 | LinkedSignalNode, 13 | LinkedSignalGetter, 14 | createLinkedSignal, 15 | linkedSignalSetFn, 16 | linkedSignalUpdateFn, 17 | } from './src/linked_signal'; 18 | export { ValueEqualityFn, defaultEquals } from './src/equality'; 19 | export { setThrowInvalidWriteToSignalError } from './src/errors'; 20 | export { 21 | REACTIVE_NODE, 22 | Reactive, 23 | ReactiveNode, 24 | SIGNAL, 25 | consumerAfterComputation, 26 | consumerBeforeComputation, 27 | consumerDestroy, 28 | consumerMarkDirty, 29 | consumerPollProducersForChange, 30 | getActiveConsumer, 31 | isInNotificationPhase, 32 | isReactive, 33 | producerAccessed, 34 | producerIncrementEpoch, 35 | producerMarkClean, 36 | producerNotifyConsumers, 37 | producerUpdateValueVersion, 38 | producerUpdatesAllowed, 39 | setActiveConsumer, 40 | } from './src/graph'; 41 | export { 42 | SIGNAL_NODE, 43 | SignalGetter, 44 | SignalNode, 45 | createSignal, 46 | runPostSignalSetFn, 47 | setPostSignalSetFn, 48 | signalSetFn, 49 | signalUpdateFn, 50 | } from './src/signal'; 51 | export { 52 | Watch, 53 | WatchCleanupFn, 54 | WatchCleanupRegisterFn, 55 | createWatch, 56 | } from './src/watch'; 57 | export { setAlternateWeakRefImpl } from './src/weak_ref'; 58 | -------------------------------------------------------------------------------- /src/primitives/signals/src/computed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { defaultEquals, ValueEqualityFn } from './equality'; 10 | import { 11 | consumerAfterComputation, 12 | consumerBeforeComputation, 13 | producerAccessed, 14 | producerUpdateValueVersion, 15 | REACTIVE_NODE, 16 | ReactiveNode, 17 | setActiveConsumer, 18 | SIGNAL, 19 | } from './graph'; 20 | 21 | /** 22 | * A computation, which derives a value from a declarative reactive expression. 23 | * 24 | * `Computed`s are both producers and consumers of reactivity. 25 | */ 26 | export interface ComputedNode extends ReactiveNode { 27 | /** 28 | * Current value of the computation, or one of the sentinel values above (`UNSET`, `COMPUTING`, 29 | * `ERROR`). 30 | */ 31 | value: T; 32 | 33 | /** 34 | * If `value` is `ERRORED`, the error caught from the last computation attempt which will 35 | * be re-thrown. 36 | */ 37 | error: unknown; 38 | 39 | /** 40 | * The computation function which will produce a new value. 41 | */ 42 | computation: () => T; 43 | 44 | equal: ValueEqualityFn; 45 | } 46 | 47 | export type ComputedGetter = (() => T) & { 48 | [SIGNAL]: ComputedNode; 49 | }; 50 | 51 | /** 52 | * Create a computed signal which derives a reactive value from an expression. 53 | */ 54 | export function createComputed(computation: () => T): ComputedGetter { 55 | const node: ComputedNode = Object.create(COMPUTED_NODE); 56 | node.computation = computation; 57 | 58 | const computed = () => { 59 | // Check if the value needs updating before returning it. 60 | producerUpdateValueVersion(node); 61 | 62 | // Record that someone looked at this signal. 63 | producerAccessed(node); 64 | 65 | if (node.value === ERRORED) { 66 | throw node.error; 67 | } 68 | 69 | return node.value; 70 | }; 71 | (computed as ComputedGetter)[SIGNAL] = node; 72 | return computed as unknown as ComputedGetter; 73 | } 74 | 75 | /** 76 | * A dedicated symbol used before a computed value has been calculated for the first time. 77 | * Explicitly typed as `any` so we can use it as signal's value. 78 | */ 79 | export const UNSET: any = /* @__PURE__ */ Symbol('UNSET'); 80 | 81 | /** 82 | * A dedicated symbol used in place of a computed signal value to indicate that a given computation 83 | * is in progress. Used to detect cycles in computation chains. 84 | * Explicitly typed as `any` so we can use it as signal's value. 85 | */ 86 | export const COMPUTING: any = /* @__PURE__ */ Symbol('COMPUTING'); 87 | 88 | /** 89 | * A dedicated symbol used in place of a computed signal value to indicate that a given computation 90 | * failed. The thrown error is cached until the computation gets dirty again. 91 | * Explicitly typed as `any` so we can use it as signal's value. 92 | */ 93 | export const ERRORED: any = /* @__PURE__ */ Symbol('ERRORED'); 94 | 95 | // Note: Using an IIFE here to ensure that the spread assignment is not considered 96 | // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`. 97 | // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved. 98 | const COMPUTED_NODE = /* @__PURE__ */ (() => ({ 99 | ...REACTIVE_NODE, 100 | value: UNSET, 101 | dirty: true, 102 | error: null, 103 | equal: defaultEquals, 104 | kind: 'computed', 105 | 106 | producerMustRecompute(node: ComputedNode): boolean { 107 | // Force a recomputation if there's no current value, or if the current value is in the 108 | // process of being calculated (which should throw an error). 109 | return node.value === UNSET || node.value === COMPUTING; 110 | }, 111 | 112 | producerRecomputeValue(node: ComputedNode): void { 113 | if (node.value === COMPUTING) { 114 | // Our computation somehow led to a cyclic read of itself. 115 | throw new Error('Detected cycle in computations.'); 116 | } 117 | 118 | const oldValue = node.value; 119 | node.value = COMPUTING; 120 | 121 | const prevConsumer = consumerBeforeComputation(node); 122 | let newValue: unknown; 123 | let wasEqual = false; 124 | try { 125 | newValue = node.computation(); 126 | // We want to mark this node as errored if calling `equal` throws; however, we don't want 127 | // to track any reactive reads inside `equal`. 128 | setActiveConsumer(null); 129 | wasEqual = 130 | oldValue !== UNSET && 131 | oldValue !== ERRORED && 132 | newValue !== ERRORED && 133 | node.equal(oldValue, newValue); 134 | } catch (err) { 135 | newValue = ERRORED; 136 | node.error = err; 137 | } finally { 138 | consumerAfterComputation(node, prevConsumer); 139 | } 140 | 141 | if (wasEqual) { 142 | // No change to `valueVersion` - old and new values are 143 | // semantically equivalent. 144 | node.value = oldValue; 145 | return; 146 | } 147 | 148 | node.value = newValue; 149 | node.version++; 150 | }, 151 | }))(); 152 | -------------------------------------------------------------------------------- /src/primitives/signals/src/equality.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | /** 10 | * A comparison function which can determine if two values are equal. 11 | */ 12 | export type ValueEqualityFn = (a: T, b: T) => boolean; 13 | 14 | /** 15 | * The default equality function used for `signal` and `computed`, which uses referential equality. 16 | */ 17 | export function defaultEquals(a: T, b: T) { 18 | return Object.is(a, b); 19 | } 20 | -------------------------------------------------------------------------------- /src/primitives/signals/src/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import type { SignalNode } from './signal'; 10 | 11 | function defaultThrowError(): never { 12 | throw new Error(); 13 | } 14 | 15 | let throwInvalidWriteToSignalErrorFn: (node: SignalNode) => never = 16 | defaultThrowError; 17 | 18 | export function throwInvalidWriteToSignalError(node: SignalNode) { 19 | throwInvalidWriteToSignalErrorFn(node); 20 | } 21 | 22 | export function setThrowInvalidWriteToSignalError( 23 | fn: (node: SignalNode) => never, 24 | ): void { 25 | throwInvalidWriteToSignalErrorFn = fn; 26 | } 27 | -------------------------------------------------------------------------------- /src/primitives/signals/src/linked_signal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { COMPUTING, ERRORED, UNSET } from './computed'; 10 | import { defaultEquals, ValueEqualityFn } from './equality'; 11 | import { 12 | consumerAfterComputation, 13 | consumerBeforeComputation, 14 | producerAccessed, 15 | producerMarkClean, 16 | producerUpdateValueVersion, 17 | REACTIVE_NODE, 18 | ReactiveNode, 19 | SIGNAL, 20 | } from './graph'; 21 | import { signalSetFn, signalUpdateFn } from './signal'; 22 | 23 | export type ComputationFn = ( 24 | source: S, 25 | previous?: { source: S; value: D }, 26 | ) => D; 27 | 28 | export interface LinkedSignalNode extends ReactiveNode { 29 | /** 30 | * Value of the source signal that was used to derive the computed value. 31 | */ 32 | sourceValue: S; 33 | 34 | /** 35 | * Current state value, or one of the sentinel values (`UNSET`, `COMPUTING`, 36 | * `ERROR`). 37 | */ 38 | value: D; 39 | 40 | /** 41 | * If `value` is `ERRORED`, the error caught from the last computation attempt which will 42 | * be re-thrown. 43 | */ 44 | error: unknown; 45 | 46 | /** 47 | * The source function represents reactive dependency based on which the linked state is reset. 48 | */ 49 | source: () => S; 50 | 51 | /** 52 | * The computation function which will produce a new value based on the source and, optionally - previous values. 53 | */ 54 | computation: ComputationFn; 55 | 56 | equal: ValueEqualityFn; 57 | } 58 | 59 | export type LinkedSignalGetter = (() => D) & { 60 | [SIGNAL]: LinkedSignalNode; 61 | }; 62 | 63 | export function createLinkedSignal( 64 | sourceFn: () => S, 65 | computationFn: ComputationFn, 66 | equalityFn?: ValueEqualityFn, 67 | ): LinkedSignalGetter { 68 | const node: LinkedSignalNode = Object.create(LINKED_SIGNAL_NODE); 69 | 70 | node.source = sourceFn; 71 | node.computation = computationFn; 72 | if (equalityFn != undefined) { 73 | node.equal = equalityFn; 74 | } 75 | 76 | const linkedSignalGetter = () => { 77 | // Check if the value needs updating before returning it. 78 | producerUpdateValueVersion(node); 79 | 80 | // Record that someone looked at this signal. 81 | producerAccessed(node); 82 | 83 | if (node.value === ERRORED) { 84 | throw node.error; 85 | } 86 | 87 | return node.value; 88 | }; 89 | 90 | const getter = linkedSignalGetter as LinkedSignalGetter; 91 | getter[SIGNAL] = node; 92 | 93 | return getter; 94 | } 95 | 96 | export function linkedSignalSetFn( 97 | node: LinkedSignalNode, 98 | newValue: D, 99 | ) { 100 | producerUpdateValueVersion(node); 101 | signalSetFn(node, newValue); 102 | producerMarkClean(node); 103 | } 104 | 105 | export function linkedSignalUpdateFn( 106 | node: LinkedSignalNode, 107 | updater: (value: D) => D, 108 | ): void { 109 | producerUpdateValueVersion(node); 110 | signalUpdateFn(node, updater); 111 | producerMarkClean(node); 112 | } 113 | 114 | // Note: Using an IIFE here to ensure that the spread assignment is not considered 115 | // a side-effect, ending up preserving `LINKED_SIGNAL_NODE` and `REACTIVE_NODE`. 116 | // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved. 117 | export const LINKED_SIGNAL_NODE = /* @__PURE__ */ (() => ({ 118 | ...REACTIVE_NODE, 119 | value: UNSET, 120 | dirty: true, 121 | error: null, 122 | equal: defaultEquals, 123 | 124 | producerMustRecompute(node: LinkedSignalNode): boolean { 125 | // Force a recomputation if there's no current value, or if the current value is in the 126 | // process of being calculated (which should throw an error). 127 | return node.value === UNSET || node.value === COMPUTING; 128 | }, 129 | 130 | producerRecomputeValue(node: LinkedSignalNode): void { 131 | if (node.value === COMPUTING) { 132 | // Our computation somehow led to a cyclic read of itself. 133 | throw new Error('Detected cycle in computations.'); 134 | } 135 | 136 | const oldValue = node.value; 137 | node.value = COMPUTING; 138 | 139 | const prevConsumer = consumerBeforeComputation(node); 140 | let newValue: unknown; 141 | try { 142 | const newSourceValue = node.source(); 143 | const prev = 144 | oldValue === UNSET || oldValue === ERRORED 145 | ? undefined 146 | : { 147 | source: node.sourceValue, 148 | value: oldValue, 149 | }; 150 | newValue = node.computation(newSourceValue, prev); 151 | node.sourceValue = newSourceValue; 152 | } catch (err) { 153 | newValue = ERRORED; 154 | node.error = err; 155 | } finally { 156 | consumerAfterComputation(node, prevConsumer); 157 | } 158 | 159 | if ( 160 | oldValue !== UNSET && 161 | newValue !== ERRORED && 162 | node.equal(oldValue, newValue) 163 | ) { 164 | // No change to `valueVersion` - old and new values are 165 | // semantically equivalent. 166 | node.value = oldValue; 167 | return; 168 | } 169 | 170 | node.value = newValue; 171 | node.version++; 172 | }, 173 | }))(); 174 | -------------------------------------------------------------------------------- /src/primitives/signals/src/signal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { defaultEquals, ValueEqualityFn } from './equality'; 10 | import { throwInvalidWriteToSignalError } from './errors'; 11 | import { 12 | producerAccessed, 13 | producerIncrementEpoch, 14 | producerNotifyConsumers, 15 | producerUpdatesAllowed, 16 | REACTIVE_NODE, 17 | ReactiveNode, 18 | SIGNAL, 19 | } from './graph'; 20 | 21 | // Required as the signals library is in a separate package, so we need to explicitly ensure the 22 | // global `ngDevMode` type is defined. 23 | declare const ngDevMode: boolean | undefined; 24 | 25 | /** 26 | * If set, called after `WritableSignal`s are updated. 27 | * 28 | * This hook can be used to achieve various effects, such as running effects synchronously as part 29 | * of setting a signal. 30 | */ 31 | let postSignalSetFn: (() => void) | null = null; 32 | 33 | export interface SignalNode extends ReactiveNode { 34 | value: T; 35 | equal: ValueEqualityFn; 36 | } 37 | 38 | export type SignalBaseGetter = (() => T) & { readonly [SIGNAL]: unknown }; 39 | 40 | // Note: Closure *requires* this to be an `interface` and not a type, which is why the 41 | // `SignalBaseGetter` type exists to provide the correct shape. 42 | export interface SignalGetter extends SignalBaseGetter { 43 | readonly [SIGNAL]: SignalNode; 44 | } 45 | 46 | /** 47 | * Create a `Signal` that can be set or updated directly. 48 | */ 49 | export function createSignal(initialValue: T): SignalGetter { 50 | const node: SignalNode = Object.create(SIGNAL_NODE); 51 | node.value = initialValue; 52 | const getter = (() => { 53 | producerAccessed(node); 54 | return node.value; 55 | }) as SignalGetter; 56 | (getter as any)[SIGNAL] = node; 57 | return getter; 58 | } 59 | 60 | export function setPostSignalSetFn( 61 | fn: (() => void) | null, 62 | ): (() => void) | null { 63 | const prev = postSignalSetFn; 64 | postSignalSetFn = fn; 65 | return prev; 66 | } 67 | 68 | export function signalGetFn(this: SignalNode): T { 69 | producerAccessed(this); 70 | return this.value; 71 | } 72 | 73 | export function signalSetFn(node: SignalNode, newValue: T) { 74 | if (!producerUpdatesAllowed()) { 75 | throwInvalidWriteToSignalError(node); 76 | } 77 | 78 | if (!node.equal(node.value, newValue)) { 79 | node.value = newValue; 80 | signalValueChanged(node); 81 | } 82 | } 83 | 84 | export function signalUpdateFn( 85 | node: SignalNode, 86 | updater: (value: T) => T, 87 | ): void { 88 | if (!producerUpdatesAllowed()) { 89 | throwInvalidWriteToSignalError(node); 90 | } 91 | 92 | signalSetFn(node, updater(node.value)); 93 | } 94 | 95 | export function runPostSignalSetFn(): void { 96 | postSignalSetFn?.(); 97 | } 98 | 99 | // Note: Using an IIFE here to ensure that the spread assignment is not considered 100 | // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`. 101 | // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved. 102 | export const SIGNAL_NODE: SignalNode = /* @__PURE__ */ (() => ({ 103 | ...REACTIVE_NODE, 104 | equal: defaultEquals, 105 | value: undefined, 106 | kind: 'signal', 107 | }))(); 108 | 109 | function signalValueChanged(node: SignalNode): void { 110 | node.version++; 111 | producerIncrementEpoch(); 112 | producerNotifyConsumers(node); 113 | postSignalSetFn?.(); 114 | } 115 | -------------------------------------------------------------------------------- /src/primitives/signals/src/watch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | import { 10 | consumerAfterComputation, 11 | consumerBeforeComputation, 12 | consumerDestroy, 13 | consumerMarkDirty, 14 | consumerPollProducersForChange, 15 | isInNotificationPhase, 16 | REACTIVE_NODE, 17 | ReactiveNode, 18 | SIGNAL, 19 | } from './graph'; 20 | 21 | /** 22 | * A cleanup function that can be optionally registered from the watch logic. If registered, the 23 | * cleanup logic runs before the next watch execution. 24 | */ 25 | export type WatchCleanupFn = () => void; 26 | 27 | /** 28 | * A callback passed to the watch function that makes it possible to register cleanup logic. 29 | */ 30 | export type WatchCleanupRegisterFn = (cleanupFn: WatchCleanupFn) => void; 31 | 32 | export interface Watch { 33 | notify(): void; 34 | 35 | /** 36 | * Execute the reactive expression in the context of this `Watch` consumer. 37 | * 38 | * Should be called by the user scheduling algorithm when the provided 39 | * `schedule` hook is called by `Watch`. 40 | */ 41 | run(): void; 42 | 43 | cleanup(): void; 44 | 45 | /** 46 | * Destroy the watcher: 47 | * - disconnect it from the reactive graph; 48 | * - mark it as destroyed so subsequent run and notify operations are noop. 49 | */ 50 | destroy(): void; 51 | 52 | [SIGNAL]: WatchNode; 53 | } 54 | export interface WatchNode extends ReactiveNode { 55 | hasRun: boolean; 56 | fn: ((onCleanup: WatchCleanupRegisterFn) => void) | null; 57 | schedule: ((watch: Watch) => void) | null; 58 | cleanupFn: WatchCleanupFn; 59 | ref: Watch; 60 | } 61 | 62 | export function createWatch( 63 | fn: (onCleanup: WatchCleanupRegisterFn) => void, 64 | schedule: (watch: Watch) => void, 65 | allowSignalWrites: boolean, 66 | ): Watch { 67 | const node: WatchNode = Object.create(WATCH_NODE); 68 | if (allowSignalWrites) { 69 | node.consumerAllowSignalWrites = true; 70 | } 71 | 72 | node.fn = fn; 73 | node.schedule = schedule; 74 | 75 | const registerOnCleanup = (cleanupFn: WatchCleanupFn) => { 76 | node.cleanupFn = cleanupFn; 77 | }; 78 | 79 | function isWatchNodeDestroyed(node: WatchNode) { 80 | return node.fn === null && node.schedule === null; 81 | } 82 | 83 | function destroyWatchNode(node: WatchNode) { 84 | if (!isWatchNodeDestroyed(node)) { 85 | consumerDestroy(node); // disconnect watcher from the reactive graph 86 | node.cleanupFn(); 87 | 88 | // nullify references to the integration functions to mark node as destroyed 89 | node.fn = null; 90 | node.schedule = null; 91 | node.cleanupFn = NOOP_CLEANUP_FN; 92 | } 93 | } 94 | 95 | const run = () => { 96 | if (node.fn === null) { 97 | // trying to run a destroyed watch is noop 98 | return; 99 | } 100 | 101 | if (isInNotificationPhase()) { 102 | throw new Error( 103 | `Schedulers cannot synchronously execute watches while scheduling.`, 104 | ); 105 | } 106 | 107 | node.dirty = false; 108 | if (node.hasRun && !consumerPollProducersForChange(node)) { 109 | return; 110 | } 111 | node.hasRun = true; 112 | 113 | const prevConsumer = consumerBeforeComputation(node); 114 | try { 115 | node.cleanupFn(); 116 | node.cleanupFn = NOOP_CLEANUP_FN; 117 | node.fn(registerOnCleanup); 118 | } finally { 119 | consumerAfterComputation(node, prevConsumer); 120 | } 121 | }; 122 | 123 | node.ref = { 124 | notify: () => consumerMarkDirty(node), 125 | run, 126 | cleanup: () => node.cleanupFn(), 127 | destroy: () => destroyWatchNode(node), 128 | [SIGNAL]: node, 129 | }; 130 | 131 | return node.ref; 132 | } 133 | 134 | const NOOP_CLEANUP_FN: WatchCleanupFn = () => {}; 135 | 136 | // Note: Using an IIFE here to ensure that the spread assignment is not considered 137 | // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`. 138 | // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved. 139 | const WATCH_NODE: Partial = /* @__PURE__ */ (() => ({ 140 | ...REACTIVE_NODE, 141 | consumerIsAlwaysLive: true, 142 | consumerAllowSignalWrites: false, 143 | consumerMarkedDirty: (node: WatchNode) => { 144 | if (node.schedule !== null) { 145 | node.schedule(node.ref); 146 | } 147 | }, 148 | hasRun: false, 149 | cleanupFn: NOOP_CLEANUP_FN, 150 | }))(); 151 | -------------------------------------------------------------------------------- /src/primitives/signals/src/weak_ref.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | 9 | export function setAlternateWeakRefImpl(impl: unknown) { 10 | // TODO: remove this function 11 | } 12 | -------------------------------------------------------------------------------- /test/fixture/demo.ts: -------------------------------------------------------------------------------- 1 | import { Injector, inject } from 'static-injector'; 2 | 3 | class Main { 4 | child = inject(Child); 5 | } 6 | class Child { 7 | output() { 8 | return 'hello world'; 9 | } 10 | } 11 | let injector = Injector.create({ providers: [Main, Child] }); 12 | const instance = injector.get(Main); 13 | console.log(instance.child.output()); 14 | -------------------------------------------------------------------------------- /test/fixture/destory.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector, R3Injector } from 'static-injector'; 2 | let isDestroy = false; 3 | export class MyClass { 4 | ngOnDestroy(): void { 5 | isDestroy = true; 6 | } 7 | } 8 | 9 | let injector = Injector.create({ 10 | providers: [{ provide: MyClass }], 11 | }) as R3Injector; 12 | injector.get(MyClass); 13 | injector.destroy(); 14 | export { isDestroy }; 15 | -------------------------------------------------------------------------------- /test/fixture/hello-without-provide-object.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from 'static-injector'; 2 | export class MyClass { 3 | hello() { 4 | return 'hello'; 5 | } 6 | } 7 | let injector = Injector.create({ providers: [MyClass] }); 8 | export const instance = injector.get(MyClass); 9 | -------------------------------------------------------------------------------- /test/fixture/hello.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from 'static-injector'; 2 | export class MyClass { 3 | hello() { 4 | return 'hello'; 5 | } 6 | } 7 | let injector = Injector.create({ providers: [{ provide: MyClass }] }); 8 | export const instance = injector.get(MyClass); 9 | -------------------------------------------------------------------------------- /test/fixture/inherit.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from 'static-injector'; 2 | export class Parent { 3 | name = 'parent'; 4 | hello() { 5 | return ''; 6 | } 7 | } 8 | export class MyClass extends Parent { 9 | hello() { 10 | return 'hello' + this.name; 11 | } 12 | } 13 | let injector = Injector.create({ providers: [{ provide: MyClass }] }); 14 | export const instance = injector.get(MyClass); 15 | -------------------------------------------------------------------------------- /test/fixture/inject-class.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injector, INJECTOR_SCOPE } from 'static-injector'; 2 | export class MyClass { 3 | private injectClass = inject(InjectClass); 4 | private rootInjectClass = inject(RootInjectClass); 5 | constructor() {} 6 | out() { 7 | return { 8 | injectClass: this.injectClass, 9 | rootInjectClass: this.rootInjectClass, 10 | }; 11 | } 12 | } 13 | export class InjectClass { 14 | name = 'InjectClass'; 15 | } 16 | export class RootInjectClass { 17 | static injectOptions = { providedIn: 'root' }; 18 | name = 'RootInjectClass'; 19 | } 20 | 21 | let injector = Injector.create({ 22 | providers: [ 23 | { provide: MyClass }, 24 | { provide: InjectClass }, 25 | { 26 | provide: INJECTOR_SCOPE, 27 | useValue: 'root', 28 | }, 29 | ], 30 | }); 31 | export const instance = injector.get(MyClass); 32 | -------------------------------------------------------------------------------- /test/fixture/inject-function.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injectable, Injector, INJECTOR_SCOPE } from 'static-injector'; 2 | export class MyClass { 3 | private injectClass = inject(InjectClass); 4 | private rootInjectClass = inject(RootInjectClass); 5 | 6 | constructor() {} 7 | out() { 8 | return { 9 | injectClass: this.injectClass, 10 | rootInjectClass: this.rootInjectClass, 11 | }; 12 | } 13 | } 14 | export class InjectClass { 15 | name = 'InjectClass'; 16 | } 17 | 18 | export class RootInjectClass { 19 | static injectOptions = { providedIn: 'root' }; 20 | name = 'RootInjectClass'; 21 | } 22 | 23 | let injector = Injector.create({ 24 | providers: [ 25 | { provide: MyClass }, 26 | { provide: InjectClass }, 27 | { 28 | provide: INJECTOR_SCOPE, 29 | useValue: 'root', 30 | }, 31 | ], 32 | }); 33 | export const instance = injector.get(MyClass); 34 | -------------------------------------------------------------------------------- /test/fixture/injectable.ts: -------------------------------------------------------------------------------- 1 | import { 2 | inject, 3 | Injectable, 4 | InjectionToken, 5 | Injector, 6 | INJECTOR_SCOPE, 7 | RootStaticInjectOptions 8 | } from 'static-injector'; 9 | let token = new InjectionToken('token'); 10 | 11 | export class MyClass extends RootStaticInjectOptions{ 12 | private token = inject(token); 13 | out() { 14 | return { token: this.token }; 15 | } 16 | } 17 | let injector = Injector.create({ 18 | providers: [ 19 | { provide: token, useValue: 111 }, 20 | { provide: INJECTOR_SCOPE, useValue: 'root' }, 21 | ], 22 | }); 23 | export const instance = injector.get(MyClass); 24 | -------------------------------------------------------------------------------- /test/fixture/injection-token.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken, Injector, INJECTOR_SCOPE } from 'static-injector'; 2 | 3 | let token = new InjectionToken('token', { 4 | providedIn: 'root', 5 | factory: () => 'myToken', 6 | }); 7 | let injector = Injector.create({ 8 | providers: [{ provide: INJECTOR_SCOPE, useValue: 'root' }], 9 | }); 10 | let value = injector.get(token); 11 | let tokenToString = token.toString(); 12 | export { value, tokenToString }; 13 | -------------------------------------------------------------------------------- /test/fixture/main1.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector, inject } from 'static-injector'; 2 | import * as sub from './sub1'; 3 | 4 | export class Main1Class { 5 | private sub = inject(sub.Sub1Class); 6 | constructor() {} 7 | hello() { 8 | return this.sub.hello(); 9 | } 10 | } 11 | let injector = Injector.create({ 12 | providers: [{ provide: Main1Class }, { provide: sub.Sub1Class }], 13 | }); 14 | export const instance = injector.get(Main1Class); 15 | -------------------------------------------------------------------------------- /test/fixture/multi-provider.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken, Injector } from 'static-injector'; 2 | let token = new InjectionToken('token'); 3 | let injector = Injector.create({ 4 | providers: [ 5 | { provide: token, useValue: 123, multi: true }, 6 | { provide: token, useValue: 456, multi: true }, 7 | ], 8 | }); 9 | 10 | let list = injector.get(token); 11 | export { list }; 12 | -------------------------------------------------------------------------------- /test/fixture/other-decorator.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from 'static-injector'; 2 | 3 | function OtherDecorator() { 4 | return function (target: any) {}; 5 | } 6 | 7 | @OtherDecorator() 8 | export class OnlyOtherClass { 9 | hello() { 10 | return 'hello'; 11 | } 12 | } 13 | 14 | export const result = new OnlyOtherClass().hello(); 15 | @OtherDecorator() 16 | 17 | export class BothClass { 18 | hello() { 19 | return 'hello'; 20 | } 21 | } 22 | let injector = Injector.create({ providers: [{ provide: BothClass }] }); 23 | export const instance = injector.get(BothClass); 24 | -------------------------------------------------------------------------------- /test/fixture/parameters-decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Inject, 3 | Injectable, 4 | InjectionToken, 5 | Injector, 6 | Optional, 7 | Self, 8 | SkipSelf, 9 | inject, 10 | } from 'static-injector'; 11 | 12 | export class MyClass { 13 | private token1 = inject(token1); 14 | private token1WithInjectorL2 = inject(token1, { skipSelf: true }); 15 | private token1WithSelf = inject(token1, { self: true }); 16 | constructor() {} 17 | out() { 18 | return { 19 | token1: this.token1, 20 | token1WithInjectorL2: this.token1WithInjectorL2, 21 | token1WithSelf: this.token1WithSelf, 22 | }; 23 | } 24 | } 25 | let token1 = new InjectionToken('token1'); 26 | let injectorL1 = Injector.create({ 27 | providers: [{ provide: token1, useValue: 1 }], 28 | }); 29 | let injectorL2 = Injector.create({ 30 | providers: [{ provide: token1, useValue: 2 }], 31 | parent: injectorL1, 32 | }); 33 | let injectorL3 = Injector.create({ 34 | providers: [{ provide: MyClass }, { provide: token1, useValue: 3 }], 35 | parent: injectorL2, 36 | }); 37 | export const instance = injectorL3.get(MyClass); 38 | -------------------------------------------------------------------------------- /test/fixture/provider.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injector, Optional, inject } from 'static-injector'; 2 | 3 | export class MyClass { 4 | private useClassClass = inject(UseClassClass); 5 | private useFactoryClass = inject(UseFactoryClass); 6 | private useExistingClass = inject(UseExistingClass); 7 | private classWithDeps = inject(ClassWithDeps); 8 | constructor() {} 9 | out() { 10 | return { 11 | useClassClass: this.useClassClass, 12 | useFactoryClass: this.useFactoryClass, 13 | useExistingClass: this.useExistingClass, 14 | classWithDeps: this.classWithDeps, 15 | }; 16 | } 17 | } 18 | 19 | export class UseClassClass { 20 | name = 'UseClassClass'; 21 | } 22 | 23 | export class UseFactoryClass { 24 | name = ''; 25 | constructor( 26 | private input: string, 27 | public noValue: string, 28 | public injectValue: string, 29 | ) { 30 | this.name = input; 31 | } 32 | } 33 | 34 | export class UseExistingClass { 35 | name = 'noToBeUsed'; 36 | constructor(private input: string) {} 37 | } 38 | 39 | export class ClassWithDeps { 40 | constructor(public name: string) {} 41 | } 42 | let injector = Injector.create({ 43 | providers: [ 44 | { provide: MyClass }, 45 | { provide: UseClassClass, useClass: UseClassClass }, 46 | { 47 | provide: ClassWithDeps, 48 | useClass: ClassWithDeps, 49 | deps: ['inputValueToken'], 50 | }, 51 | { provide: 'inputValueToken', useValue: 'inputValue' }, 52 | { 53 | provide: UseFactoryClass, 54 | useFactory: (value: string, noValue: string, injectValue: string) => { 55 | return new UseFactoryClass(value, noValue, injectValue); 56 | }, 57 | deps: [ 58 | 'inputValueToken', 59 | [new Optional(), 'test'], 60 | [new Inject('inputValueToken')], 61 | ], 62 | }, 63 | { 64 | provide: UseExistingClass, 65 | useExisting: UseFactoryClass, 66 | }, 67 | ], 68 | }); 69 | export const instance = injector.get(MyClass); 70 | -------------------------------------------------------------------------------- /test/fixture/sub-class.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from 'static-injector'; 2 | 3 | export class FirstClass { 4 | init() { 5 | 6 | class SecondClass { 7 | name = 'second'; 8 | } 9 | 10 | return Injector.create({ providers: [{ provide: SecondClass }] }).get( 11 | SecondClass 12 | ).name; 13 | } 14 | } 15 | export const result = new FirstClass().init(); 16 | -------------------------------------------------------------------------------- /test/fixture/sub1.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from 'static-injector'; 2 | 3 | export class Sub1Class { 4 | hello() { 5 | return 'hello'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/import/destroy.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { isDestroy } from '../fixture/destory'; 3 | 4 | describe('destroy', () => { 5 | it('destroy', () => { 6 | expect(isDestroy).eq(true); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/import/error.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Injector } from 'static-injector'; 3 | 4 | class NoProvideError {} 5 | 6 | describe('noProviderError', () => { 7 | it('main', () => { 8 | let injector = Injector.create({ providers: [] }); 9 | try { 10 | injector.get(NoProvideError); 11 | } catch (error) { 12 | expect(true).eq(true); 13 | return; 14 | } 15 | throw new Error(''); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/import/hello.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance } from '../fixture/hello'; 3 | import { instance as instance2 } from '../fixture/hello-without-provide-object'; 4 | describe('hello', () => { 5 | it('hello', () => { 6 | expect(instance.hello()).eq('hello'); 7 | }); 8 | it('hello-without-provide-object', () => { 9 | expect(instance2.hello()).eq('hello'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/import/inherit.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance } from '../fixture/inherit'; 3 | describe('inherit', () => { 4 | it('inherit', () => { 5 | expect(instance.hello()).eq('helloparent'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /test/import/inject-class.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance } from '../fixture/inject-class'; 3 | describe('inject-class', () => { 4 | it('inject-class', () => { 5 | let out = instance.out(); 6 | expect(out.injectClass.name).eq('InjectClass'); 7 | expect(out.rootInjectClass.name).eq('RootInjectClass'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/import/inject-function.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance } from '../fixture/inject-function'; 3 | describe('inject(xxx)', () => { 4 | it('inject(xxx)', () => { 5 | let out = instance.out(); 6 | expect(out.injectClass.name).eq('InjectClass'); 7 | expect(out.rootInjectClass.name).eq('RootInjectClass'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/import/injectable.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance } from '../fixture/injectable'; 3 | describe('injectable', () => { 4 | it('injectable', () => { 5 | let out = instance.out(); 6 | expect(out.token).eq(111); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/import/injection-token.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { tokenToString, value } from '../fixture/injection-token'; 3 | describe('injection-token', () => { 4 | it('injection-token', () => { 5 | expect(value).eq('myToken'); 6 | expect(tokenToString).eq('InjectionToken token'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/import/main1.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance } from '../fixture/main1'; 3 | describe('main1', () => { 4 | it('default', () => { 5 | expect(instance.hello()).eq('hello'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /test/import/multi-provider.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { list } from '../fixture/multi-provider'; 3 | 4 | describe('multi-provider', () => { 5 | it('multi-provider', () => { 6 | expect(list).deep.eq([123, 456]); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/import/other-decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance, result } from '../fixture/other-decorator'; 3 | describe('other-decorator', () => { 4 | it('only-other', () => { 5 | expect(result).eq('hello'); 6 | }); 7 | it('both', () => { 8 | expect(instance.hello()).eq('hello'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/import/parameters-decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance } from '../fixture/parameters-decorator'; 3 | describe('parameters-decorator', () => { 4 | it('parameters-decorator', () => { 5 | let out = instance.out(); 6 | expect(out.token1).eq(3); 7 | expect(out.token1WithInjectorL2).eq(2); 8 | expect(out.token1WithSelf).eq(3); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/import/provider.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { instance, UseFactoryClass } from '../fixture/provider'; 3 | describe('provider', () => { 4 | it('provider', () => { 5 | let out = instance.out(); 6 | expect(out.useClassClass.name).eq('UseClassClass'); 7 | expect(out.useExistingClass instanceof UseFactoryClass).ok; 8 | expect(out.useExistingClass.name).eq('inputValue'); 9 | expect(out.useFactoryClass.name).eq('inputValue'); 10 | expect(out.useFactoryClass.name).eq('inputValue'); 11 | expect(out.useFactoryClass.injectValue).eq('inputValue'); 12 | expect(out.useFactoryClass.noValue).eq(null); 13 | expect(out.classWithDeps.name).eq('inputValue'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/import/reactivity/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { computed, signal } from 'static-injector'; 3 | 4 | describe('computed', () => { 5 | it('hello', () => { 6 | let value = computed(() => 0); 7 | expect(value()).eq(0); 8 | }); 9 | it('set', () => { 10 | let value = signal(0); 11 | let value2 = computed(() => value()); 12 | expect(value2()).eq(0); 13 | value.set(1); 14 | expect(value2()).eq(1); 15 | }); 16 | it('update', () => { 17 | let value = signal(0); 18 | let value2 = computed(() => value()); 19 | expect(value2()).eq(0); 20 | value.update((value) => value + 1); 21 | expect(value2()).eq(1); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/import/reactivity/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionScheduler, 3 | ChangeDetectionSchedulerImpl, 4 | computed, 5 | createInjector, 6 | createRootInjector, 7 | effect, 8 | EnvironmentInjector, 9 | Injector, 10 | INJECTOR_SCOPE, 11 | RootStaticInjectOptions, 12 | signal, 13 | } from 'static-injector'; 14 | 15 | describe('effect', () => { 16 | it('hello', async () => { 17 | let inejctor = Injector.create({ 18 | providers: [ 19 | { 20 | provide: ChangeDetectionScheduler, 21 | useClass: ChangeDetectionSchedulerImpl, 22 | }, 23 | { 24 | provide: INJECTOR_SCOPE, 25 | useValue: 'root', 26 | }, 27 | ], 28 | }); 29 | let p = new Promise((resolve) => { 30 | effect( 31 | () => { 32 | resolve(); 33 | }, 34 | { injector: inejctor }, 35 | ); 36 | }); 37 | await p; 38 | }); 39 | it('等待值变更', async () => { 40 | let inejctor = Injector.create({ 41 | providers: [ 42 | { 43 | provide: ChangeDetectionScheduler, 44 | useClass: ChangeDetectionSchedulerImpl, 45 | }, 46 | { 47 | provide: INJECTOR_SCOPE, 48 | useValue: 'root', 49 | }, 50 | ], 51 | }); 52 | let value1$ = signal(0); 53 | let p = new Promise((resolve) => { 54 | effect( 55 | () => { 56 | let value1 = value1$(); 57 | if (value1 === 1) { 58 | resolve(); 59 | } 60 | }, 61 | { injector: inejctor }, 62 | ); 63 | }); 64 | setTimeout(() => { 65 | value1$.set(1); 66 | }, 0); 67 | await p; 68 | }); 69 | it('等待值变更2', async () => { 70 | let inejctor = Injector.create({ 71 | providers: [ 72 | { 73 | provide: ChangeDetectionScheduler, 74 | useClass: ChangeDetectionSchedulerImpl, 75 | }, 76 | { 77 | provide: INJECTOR_SCOPE, 78 | useValue: 'root', 79 | }, 80 | ], 81 | }); 82 | let value1$ = signal(0); 83 | let value2$ = signal(0); 84 | let c1$$ = computed(() => { 85 | return value1$() + value2$(); 86 | }); 87 | let p = new Promise((resolve) => { 88 | effect( 89 | () => { 90 | let value1 = c1$$(); 91 | if (value1 === 2) { 92 | resolve(); 93 | } 94 | }, 95 | { injector: inejctor }, 96 | ); 97 | }); 98 | setTimeout(() => { 99 | value1$.set(1); 100 | }, 0); 101 | setTimeout(() => { 102 | value1$.set(2); 103 | }, 0); 104 | await p; 105 | }); 106 | it('类内构造', async () => { 107 | let inejctor = Injector.create({ 108 | providers: [ 109 | { 110 | provide: ChangeDetectionScheduler, 111 | useClass: ChangeDetectionSchedulerImpl, 112 | }, 113 | { 114 | provide: INJECTOR_SCOPE, 115 | useValue: 'root', 116 | }, 117 | ], 118 | }); 119 | let resolve: any; 120 | let p = new Promise((res) => { 121 | resolve = res; 122 | }); 123 | class Test1 extends RootStaticInjectOptions { 124 | value1$ = signal(0); 125 | value2$ = signal(0); 126 | c1$$ = computed(() => { 127 | return this.value1$() + this.value2$(); 128 | }); 129 | constructor() { 130 | super(); 131 | effect(() => { 132 | let value1 = this.c1$$(); 133 | if (value1 === 2) { 134 | resolve(); 135 | } 136 | }); 137 | setTimeout(() => { 138 | this.value1$.set(1); 139 | }, 0); 140 | setTimeout(() => { 141 | this.value1$.set(2); 142 | }, 0); 143 | } 144 | } 145 | inejctor.get(Test1); 146 | await p; 147 | }); 148 | it('destroy', async () => { 149 | let inejctor = Injector.create({ 150 | providers: [ 151 | { 152 | provide: ChangeDetectionScheduler, 153 | useClass: ChangeDetectionSchedulerImpl, 154 | }, 155 | { 156 | provide: INJECTOR_SCOPE, 157 | useValue: 'root', 158 | }, 159 | ], 160 | }); 161 | await new Promise((res) => { 162 | let ref = effect( 163 | (cFn) => { 164 | cFn(() => { 165 | res(); 166 | }); 167 | ref.destroy(); 168 | }, 169 | { injector: inejctor }, 170 | ); 171 | }); 172 | }); 173 | it('injector-destroy', async () => { 174 | let inejctor = createRootInjector({ 175 | providers: [ 176 | { 177 | provide: ChangeDetectionScheduler, 178 | useClass: ChangeDetectionSchedulerImpl, 179 | }, 180 | ], 181 | }); 182 | 183 | let subInjector = createInjector({ 184 | providers: [{ provide: EnvironmentInjector, useValue: inejctor }], 185 | parent: inejctor, 186 | }); 187 | await new Promise((res) => { 188 | let ref = effect( 189 | (cFn) => { 190 | cFn(() => { 191 | res(); 192 | }); 193 | }, 194 | { injector: subInjector }, 195 | ); 196 | setTimeout(() => { 197 | subInjector.destroy(); 198 | }, 0); 199 | }); 200 | }); 201 | }); 202 | -------------------------------------------------------------------------------- /test/import/reactivity/linked_signal.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | ChangeDetectionScheduler, 4 | ChangeDetectionSchedulerImpl, 5 | computed, 6 | effect, 7 | inject, 8 | Injector, 9 | INJECTOR_SCOPE, 10 | linkedSignal, 11 | RootStaticInjectOptions, 12 | signal, 13 | } from 'static-injector'; 14 | 15 | describe('linkedSignal', () => { 16 | it('hello', async () => { 17 | let value$ = signal(0); 18 | let ls = linkedSignal(() => value$()); 19 | ls.set(1); 20 | expect(ls()).eq(1); 21 | value$.set(2); 22 | expect(ls()).eq(2); 23 | ls.set(3); 24 | expect(ls()).eq(3); 25 | }); 26 | it('computation', () => { 27 | let value$ = signal({ value: 1 }); 28 | let value2$ = signal(0); 29 | let ls = linkedSignal({ 30 | source: () => value$(), 31 | computation: (item) => item.value + value2$(), 32 | }); 33 | expect(ls()).eq(1); 34 | value2$.set(1); 35 | expect(ls()).eq(2); 36 | value$.set({ value: 2 }); 37 | expect(ls()).eq(3); 38 | }); 39 | it('equal', () => { 40 | let value$ = signal({ value: 1 }); 41 | let ls = linkedSignal(() => value$().value, { 42 | equal: () => true, 43 | }); 44 | expect(ls()).eq(1); 45 | value$.set({ value: 2 }); 46 | expect(value$()).deep.eq({ value: 2 }); 47 | expect(ls()).eq(1); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/import/reactivity/resource.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | ChangeDetectionScheduler, 4 | ChangeDetectionSchedulerImpl, 5 | effect, 6 | inject, 7 | Injector, 8 | INJECTOR_SCOPE, 9 | resource, 10 | signal, 11 | } from 'static-injector'; 12 | 13 | describe('resource', () => { 14 | it('hello', async () => { 15 | let injector = Injector.create({ 16 | providers: [ 17 | { 18 | provide: ChangeDetectionScheduler, 19 | useClass: ChangeDetectionSchedulerImpl, 20 | }, 21 | { 22 | provide: INJECTOR_SCOPE, 23 | useValue: 'root', 24 | }, 25 | ], 26 | }); 27 | let value$ = signal(0); 28 | let res1$$ = resource({ 29 | request: () => value$(), 30 | loader: async ({ request, abortSignal }) => { 31 | return request + 1; 32 | }, 33 | injector: injector, 34 | }); 35 | expect(res1$$.isLoading()).eq(true); 36 | await new Promise((res) => { 37 | effect( 38 | () => { 39 | let isLoading = res1$$.isLoading(); 40 | if (!isLoading) { 41 | expect(res1$$.value()).eq(1); 42 | res(); 43 | } 44 | }, 45 | { injector: injector }, 46 | ); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/import/reactivity/signal.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { signal } from 'static-injector'; 3 | 4 | describe('signal', () => { 5 | it('hello', () => { 6 | let value = signal(0); 7 | expect(value()).eq(0); 8 | }); 9 | it('set', () => { 10 | let value = signal(0); 11 | value.set(1); 12 | expect(value()).eq(1); 13 | }); 14 | it('update', () => { 15 | let value = signal(0); 16 | value.update((value) => value + 1); 17 | expect(value()).eq(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/import/reactivity/untracked.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { computed, signal, untracked } from 'static-injector'; 3 | 4 | describe('untracked', () => { 5 | it('hello', () => { 6 | let value$ = signal(0); 7 | let c1$ = computed(() => untracked(() => value$())); 8 | expect(c1$()).eq(0) 9 | value$.set(1) 10 | expect(c1$()).eq(0) 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/import/sub-class.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { result } from '../fixture/sub-class'; 3 | describe('hello', () => { 4 | it('sub-class', () => { 5 | expect(result).eq('second'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | /* Basic Options */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | "target": "es2022" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 7 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 8 | "lib": [ 9 | "ESNext", 10 | "DOM" 11 | ] /* Specify library files to be included in the compilation. */, 12 | // "allowJs": true, /* Allow javascript files to be compiled. */ 13 | // "checkJs": true, /* Report errors in .js files. */ 14 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 15 | "declaration": true /* Generates corresponding '.d.ts' file. */, 16 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 17 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | // "outDir": "./dist" /* Redirect output structure to the directory. */, 20 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 21 | // "composite": true, /* Enable project compilation */ 22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 23 | // "removeComments": true, /* Do not emit comments to output. */ 24 | // "noEmit": true, /* Do not emit outputs. */ 25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | /* Strict Type-Checking Options */ 29 | "strict": false /* Enable all strict type-checking options. */, 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | /* Module Resolution Options */ 43 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 44 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, 45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 47 | // "typeRoots": [], /* List of folders to include type definitions from. */ 48 | // "types": [], /* Type declaration files to be included in compilation. */ 49 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 50 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 51 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 52 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | /* Experimental Options */ 59 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | /* Advanced Options */ 62 | "skipLibCheck": true /* Skip type checking of declaration files. */, 63 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, 64 | "paths": { 65 | "static-injector": [ 66 | "./src/import" 67 | ], 68 | "@angular/core/primitives/signals": [ 69 | "./src/primitives/signals/index.ts" 70 | ] 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tsconfig.import-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "files": ["./src/import/index.ts"], 4 | "include": [], 5 | "compilerOptions": { 6 | "outDir": "../node_modules/static-injector", 7 | "declaration": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.import.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist/es2022", 5 | "declarationDir": "dist/typings", 6 | "module": "ES2022", 7 | "target": "ES2022", 8 | "moduleResolution": "node", 9 | "strict": true 10 | }, 11 | "include": [ 12 | "./src/import/**/*.ts", 13 | "./src/primitives/**/*.ts", 14 | "./typings.d.ts" 15 | ] 16 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": {}, 3 | "include": [], 4 | "files": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.import.json" 8 | }, 9 | { 10 | "path": "./tsconfig.import-test.json" 11 | }, 12 | { 13 | "path": "./tsconfig.spec.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "strict": true 6 | }, 7 | "include": ["./test","./typings.d.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.type.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist/es2022", 5 | "declarationDir": "dist", 6 | "module": "ES2022", 7 | "target": "ES2022", 8 | "moduleResolution": "node", 9 | "strict": true, 10 | "declaration": true, 11 | "emitDeclarationOnly": true 12 | }, 13 | "files": [ 14 | "./src/import/index.ts", 15 | "./typings.d.ts" 16 | ] 17 | } -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | /** 待移除 */ 3 | const ngDevMode; 4 | const Zone 5 | type Zone = any; 6 | } 7 | --------------------------------------------------------------------------------