├── .editorconfig ├── .gitignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── projects └── ngx-resize │ ├── README.md │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ └── resize.ts │ └── public-api.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.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 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NgxResize Workspace 2 | 3 | | project | readme | 4 | | ------------ | ------------------------------------------------------------------------------------------- | 5 | | `ngx-resize` | [![README](https://img.shields.io/badge/README--green.svg)](/projects/ngx-resize/README.md) | 6 | 7 | 8 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-resize": { 7 | "projectType": "library", 8 | "root": "projects/ngx-resize", 9 | "sourceRoot": "projects/ngx-resize/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "project": "projects/ngx-resize/ng-package.json" 16 | }, 17 | "configurations": { 18 | "production": { 19 | "tsConfig": "projects/ngx-resize/tsconfig.lib.prod.json" 20 | }, 21 | "development": { 22 | "tsConfig": "projects/ngx-resize/tsconfig.lib.json" 23 | } 24 | }, 25 | "defaultConfiguration": "production" 26 | }, 27 | "test": { 28 | "builder": "@angular-devkit/build-angular:karma", 29 | "options": { 30 | "tsConfig": "projects/ngx-resize/tsconfig.spec.json", 31 | "polyfills": ["zone.js", "zone.js/testing"] 32 | } 33 | } 34 | } 35 | } 36 | }, 37 | "cli": { 38 | "analytics": "37f235fc-8873-4ce7-bc8b-fc6289e8771e" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-resize", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^16.0.0", 14 | "@angular/common": "^16.0.0", 15 | "@angular/compiler": "^16.0.0", 16 | "@angular/core": "^16.0.0", 17 | "@angular/forms": "^16.0.0", 18 | "@angular/platform-browser": "^16.0.0", 19 | "@angular/platform-browser-dynamic": "^16.0.0", 20 | "@angular/router": "^16.0.0", 21 | "rxjs": "~7.8.0", 22 | "tslib": "^2.5.0", 23 | "zone.js": "~0.13.0" 24 | }, 25 | "devDependencies": { 26 | "@angular-devkit/build-angular": "^16.0.0", 27 | "@angular/cli": "~16.0.0", 28 | "@angular/compiler-cli": "^16.0.0", 29 | "@types/jasmine": "~4.3.1", 30 | "jasmine-core": "~4.6.0", 31 | "karma": "~6.4.1", 32 | "karma-chrome-launcher": "~3.1.1", 33 | "karma-coverage": "~2.2.0", 34 | "karma-jasmine": "~5.1.0", 35 | "karma-jasmine-html-reporter": "~2.0.0", 36 | "ng-packagr": "^16.0.0", 37 | "prettier": "^2.8.7", 38 | "prettier-plugin-organize-imports": "^3.2.2", 39 | "typescript": "~4.9.5" 40 | } 41 | } -------------------------------------------------------------------------------- /projects/ngx-resize/README.md: -------------------------------------------------------------------------------- 1 | # NgxResize 2 | 3 | A service that emits changes of a DOM container on `resize` by utilizing [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) 4 | 5 | ## Installation 6 | 7 | ```shell 8 | npm install ngx-resize 9 | ``` 10 | 11 | ## Usage 12 | 13 | There are two approaches of using `ngx-resize`. Both approaches results an `Observable` 14 | 15 | ```ts 16 | export interface NgxResizeResult { 17 | readonly entries: ReadonlyArray; 18 | readonly x: number; 19 | readonly y: number; 20 | readonly width: number; 21 | readonly height: number; 22 | readonly top: number; 23 | readonly right: number; 24 | readonly bottom: number; 25 | readonly left: number; 26 | readonly dpr: number; 27 | } 28 | ``` 29 | 30 | ### `injectNgxResize()` 31 | 32 | If you're on Angular 14+ and are using `inject()` for **Dependency Injection**, you can use `injectNgxResize()` to grab the `Observable` 33 | 34 | ```ts 35 | @Component({}) 36 | export class SomeComponent { 37 | readonly resizeResult$ = injectNgxResize(); // Observable 38 | } 39 | ``` 40 | 41 | `injectNgxResize()` accepts a `Partial` and will be merged with the default global options. 42 | 43 | ```ts 44 | export interface NgxResizeOptions { 45 | /* "box" options that is passed in new ResizeObserver */ 46 | box: ResizeObserverBoxOptions; 47 | /* time in ms to debounce the events; single number for resize; can also support scroll and resize separately */ 48 | debounce: number | { scroll: number; resize: number }; 49 | /* whether to observe resize on scroll */ 50 | scroll: boolean; 51 | /* use offset size instead */ 52 | offsetSize: boolean; 53 | /* emit in NgZone or not. Default to "true" */ 54 | emitInZone: boolean; 55 | /* emit the initial DOMRect of nativeElement. Default to "false" */ 56 | emitInitialResult: boolean; 57 | } 58 | 59 | export const defaultResizeOptions: NgxResizeOptions = { 60 | box: 'content-box', 61 | scroll: false, 62 | offsetSize: false, 63 | debounce: { scroll: 50, resize: 0 }, 64 | emitInZone: true, 65 | emitInitialResult: false, 66 | }; 67 | ``` 68 | 69 | #### With `Output` 70 | 71 | Instead of getting the `Observable`, you can assign `injectNgxResize()` to an `Output` directly 72 | 73 | ```ts 74 | @Component({}) 75 | export class SomeComponent { 76 | @Output() resize = injectNgxResize(); // resize emits everytime NgxResize emits 77 | } 78 | ``` 79 | 80 | ```html 81 | 82 | 83 | ``` 84 | 85 | ### `NgxResize` 86 | 87 | If you're not using `inject()`, you can use the `NgxResize` directive 88 | 89 | ```html 90 | 91 | 95 | ``` 96 | 97 | #### With `hostDirectives` 98 | 99 | With Angular 15, you can also use `NgxResize` as a `hostDirectives` and expose `ngxResize` Output 100 | 101 | ```ts 102 | @Component({ 103 | hostDirectives: [{ directive: NgxResize, outputs: ['ngxResize'] }], 104 | }) 105 | export class SomeComponent { 106 | @HostListener('ngxResize', ['$event']) 107 | onResize(event: NgxResizeResult) { 108 | // listen for resize event from NgxResize 109 | } 110 | } 111 | ``` 112 | 113 | > Credit to @denisyilmaz for this usage 114 | 115 | ## Provide global `NgxResizeOptions` 116 | 117 | You can use `provideNgxResizeOptions()` to provide global options for a specific Component tree. If you call `provideNgxResizeOptions()` in `bootstrapApplication()` (for Standalone) and `AppModule` (for NgModule) 118 | then the options is truly global. 119 | 120 | ## Contributions 121 | 122 | All contributions of any kind are welcome. 123 | -------------------------------------------------------------------------------- /projects/ngx-resize/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-resize", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-resize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-resize", 3 | "version": "2.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/nartc/ngx-resize/tree/main/projects/ngx-resize" 7 | }, 8 | "author": { 9 | "name": "Chau Tran", 10 | "email": "nartc7789@gmail.com", 11 | "url": "https://nartc.me" 12 | }, 13 | "description": "NgxResize Service", 14 | "keywords": [ 15 | "typescript", 16 | "resize observer", 17 | "angular" 18 | ], 19 | "license": "MIT", 20 | "peerDependencies": { 21 | "@angular/common": "^14.0.0 || ^15.0.0 || ^16.0.0", 22 | "@angular/core": "^14.0.0 || ^15.0.0 || ^16.0.0" 23 | }, 24 | "dependencies": { 25 | "tslib": "^2.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /projects/ngx-resize/src/lib/resize.ts: -------------------------------------------------------------------------------- 1 | import { DOCUMENT } from '@angular/common'; 2 | import { 3 | Directive, 4 | ElementRef, 5 | EventEmitter, 6 | Inject, 7 | inject, 8 | InjectionToken, 9 | Input, 10 | NgZone, 11 | Output, 12 | type OnDestroy, 13 | type OnInit, 14 | } from '@angular/core'; 15 | import { 16 | debounceTime, 17 | fromEvent, 18 | Observable, 19 | pipe, 20 | ReplaySubject, 21 | share, 22 | takeUntil, 23 | type MonoTypeOperatorFunction, 24 | type Subscription, 25 | } from 'rxjs'; 26 | 27 | export type NgxResizeOptions = { 28 | box: ResizeObserverBoxOptions; 29 | debounce: number | { scroll: number; resize: number }; 30 | scroll: boolean; 31 | offsetSize: boolean; 32 | emitInZone: boolean; 33 | emitInitialResult: boolean; 34 | }; 35 | 36 | export const defaultResizeOptions: NgxResizeOptions = { 37 | box: 'content-box', 38 | scroll: false, 39 | offsetSize: false, 40 | debounce: { scroll: 50, resize: 0 }, 41 | emitInZone: true, 42 | emitInitialResult: false, 43 | }; 44 | 45 | export const NGX_RESIZE_OPTIONS = new InjectionToken('NgxResizeOptions', { 46 | factory: () => defaultResizeOptions, 47 | }); 48 | 49 | export function provideNgxResizeOptions(options: Partial = {}) { 50 | return { provide: NGX_RESIZE_OPTIONS, useValue: { ...defaultResizeOptions, ...options } }; 51 | } 52 | 53 | export type NgxResizeResult = { 54 | readonly entries: ReadonlyArray; 55 | readonly x: number; 56 | readonly y: number; 57 | readonly width: number; 58 | readonly height: number; 59 | readonly top: number; 60 | readonly right: number; 61 | readonly bottom: number; 62 | readonly left: number; 63 | readonly dpr: number; 64 | }; 65 | 66 | export function injectNgxResize(options: Partial = {}): Observable { 67 | const { nativeElement } = inject(ElementRef) as ElementRef; 68 | const zone = inject(NgZone); 69 | const document = inject(DOCUMENT); 70 | const mergedOptions = { ...inject(NGX_RESIZE_OPTIONS), ...options }; 71 | 72 | return createResizeStream(mergedOptions, nativeElement, document, zone); 73 | } 74 | 75 | @Directive({ selector: '[ngxResize]', standalone: true }) 76 | export class NgxResize implements OnInit, OnDestroy { 77 | @Input() ngxResizeOptions: Partial = {}; 78 | @Output() ngxResize = new EventEmitter(); 79 | 80 | constructor( 81 | private readonly host: ElementRef, 82 | private readonly zone: NgZone, 83 | @Inject(DOCUMENT) private readonly document: Document, 84 | @Inject(NGX_RESIZE_OPTIONS) private readonly resizeOptions: NgxResizeOptions 85 | ) {} 86 | 87 | private sub?: Subscription; 88 | 89 | ngOnInit() { 90 | const mergedOptions = { ...this.resizeOptions, ...this.ngxResizeOptions }; 91 | this.sub = createResizeStream(mergedOptions, this.host.nativeElement, this.document, this.zone).subscribe( 92 | this.ngxResize 93 | ); 94 | } 95 | 96 | ngOnDestroy() { 97 | this.sub?.unsubscribe(); 98 | } 99 | } 100 | 101 | // return ResizeResult observable 102 | function createResizeStream( 103 | { debounce, scroll, offsetSize, box, emitInZone, emitInitialResult }: NgxResizeOptions, 104 | nativeElement: HTMLElement, 105 | document: Document, 106 | zone: NgZone 107 | ) { 108 | const window = document.defaultView; 109 | const isSupport = !!window?.ResizeObserver; 110 | 111 | let observer: ResizeObserver; 112 | let lastBounds: Omit; 113 | let lastEntries: ResizeObserverEntry[] = []; 114 | 115 | const torndown$ = new ReplaySubject(); 116 | const scrollContainers: HTMLOrSVGElement[] | null = findScrollContainers(nativeElement, window, document.body); 117 | 118 | // set actual debounce values early, so effects know if they should react accordingly 119 | const scrollDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.scroll) : null; 120 | const resizeDebounce = debounce ? (typeof debounce === 'number' ? debounce : debounce.resize) : null; 121 | 122 | const debounceAndTorndown = (debounce: number | null): MonoTypeOperatorFunction => { 123 | return pipe(debounceTime(debounce ?? 0), takeUntil(torndown$)); 124 | }; 125 | 126 | return new Observable((subscriber) => { 127 | if (!isSupport) { 128 | subscriber.error( 129 | '[ngx-resize] your browser does not support ResizeObserver. Please consider using a polyfill' 130 | ); 131 | return; 132 | } 133 | 134 | zone.runOutsideAngular(() => { 135 | if (emitInitialResult) { 136 | const [result] = calculateResult(nativeElement, window, offsetSize, []); 137 | if (emitInZone) zone.run(() => void subscriber.next(result)); 138 | else subscriber.next(result); 139 | } 140 | 141 | const callback = (entries: ResizeObserverEntry[]) => { 142 | lastEntries = entries; 143 | const [result, size] = calculateResult(nativeElement, window, offsetSize, entries); 144 | 145 | if (emitInZone) zone.run(() => void subscriber.next(result)); 146 | else subscriber.next(result); 147 | 148 | if (!areBoundsEqual(lastBounds || {}, size)) lastBounds = size; 149 | }; 150 | 151 | const boundCallback = () => void callback(lastEntries); 152 | 153 | observer = new ResizeObserver(callback); 154 | 155 | observer.observe(nativeElement, { box }); 156 | if (scroll) { 157 | if (scrollContainers) { 158 | scrollContainers.forEach((scrollContainer) => { 159 | fromEvent(scrollContainer as HTMLElement, 'scroll', { capture: true, passive: true }) 160 | .pipe(debounceAndTorndown(scrollDebounce)) 161 | .subscribe(boundCallback); 162 | }); 163 | } 164 | 165 | fromEvent(window, 'scroll', { capture: true, passive: true }) 166 | .pipe(debounceAndTorndown(scrollDebounce)) 167 | .subscribe(boundCallback); 168 | } 169 | 170 | fromEvent(window, 'resize').pipe(debounceAndTorndown(resizeDebounce)).subscribe(boundCallback); 171 | }); 172 | 173 | return () => { 174 | if (observer) { 175 | observer.unobserve(nativeElement); 176 | observer.disconnect(); 177 | } 178 | torndown$.next(); 179 | torndown$.complete(); 180 | }; 181 | }).pipe(debounceTime(scrollDebounce ?? 0), share({ connector: () => new ReplaySubject(1) })); 182 | } 183 | 184 | function calculateResult( 185 | nativeElement: HTMLElement, 186 | window: Window, 187 | offsetSize: boolean, 188 | entries: ResizeObserverEntry[] 189 | ): [NgxResizeResult, Omit] { 190 | const { left, top, width, height, bottom, right, x, y } = nativeElement.getBoundingClientRect(); 191 | const size = { left, top, width, height, bottom, right, x, y }; 192 | 193 | if (nativeElement instanceof HTMLElement && offsetSize) { 194 | size.height = nativeElement.offsetHeight; 195 | size.width = nativeElement.offsetWidth; 196 | } 197 | 198 | Object.freeze(size); 199 | return [{ entries, dpr: window.devicePixelRatio, ...size }, size]; 200 | } 201 | 202 | // Returns a list of scroll offsets 203 | function findScrollContainers( 204 | element: HTMLOrSVGElement | null, 205 | window: Window | null, 206 | documentBody: HTMLElement 207 | ): HTMLOrSVGElement[] { 208 | const result: HTMLOrSVGElement[] = []; 209 | if (!element || !window || element === documentBody) return result; 210 | const { overflow, overflowX, overflowY } = window.getComputedStyle(element as HTMLElement); 211 | if ([overflow, overflowX, overflowY].some((prop) => prop === 'auto' || prop === 'scroll')) result.push(element); 212 | return [...result, ...findScrollContainers((element as HTMLElement).parentElement, window, documentBody)]; 213 | } 214 | 215 | // Checks if element boundaries are equal 216 | const keys: (keyof Omit)[] = [ 217 | 'x', 218 | 'y', 219 | 'top', 220 | 'bottom', 221 | 'left', 222 | 'right', 223 | 'width', 224 | 'height', 225 | ]; 226 | const areBoundsEqual = (a: Omit, b: Omit) => 227 | keys.every((key) => a[key] === b[key]); 228 | -------------------------------------------------------------------------------- /projects/ngx-resize/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-resize 3 | */ 4 | export * from './lib/resize'; 5 | -------------------------------------------------------------------------------- /projects/ngx-resize/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [] 10 | }, 11 | "exclude": ["**/*.spec.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /projects/ngx-resize/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /projects/ngx-resize/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": ["jasmine"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "paths": { 15 | "ngx-resize": ["dist/ngx-resize"] 16 | }, 17 | "declaration": false, 18 | "downlevelIteration": true, 19 | "experimentalDecorators": true, 20 | "moduleResolution": "node", 21 | "importHelpers": true, 22 | "target": "ES2022", 23 | "module": "ES2022", 24 | "useDefineForClassFields": false, 25 | "lib": ["ES2022", "dom"] 26 | }, 27 | "angularCompilerOptions": { 28 | "enableI18nLegacyMessageIdFormat": false, 29 | "strictInjectionParameters": true, 30 | "strictInputAccessModifiers": true, 31 | "strictTemplates": true 32 | } 33 | } 34 | --------------------------------------------------------------------------------