├── .nvmrc ├── packages ├── core │ ├── src │ │ ├── index.ts │ │ ├── base │ │ │ └── usecase.interface.ts │ │ └── tests │ │ │ └── example.test.ts │ ├── .gitignore │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── tsconfig.json │ ├── rollup.config.js │ └── package.json ├── data │ ├── src │ │ ├── index.ts │ │ └── tests │ │ │ └── .gitkeep │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── tsconfig.json │ ├── rollup.config.js │ └── package.json ├── di │ ├── src │ │ └── index.ts │ ├── .prettierrc │ ├── tsconfig.json │ ├── package.json │ └── rollup.config.js └── presentation │ ├── src │ ├── di │ │ └── .gitkeep │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── app.component.scss │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ └── app.component.spec.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── styles.scss │ ├── favicon.ico │ ├── tests │ │ └── example.test.ts │ ├── index.html │ ├── main.ts │ ├── test.ts │ └── polyfills.ts │ ├── .prettierrc │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json │ ├── jest.config.js │ ├── .editorconfig │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ ├── .browserslistrc │ ├── .gitignore │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ ├── karma.conf.js │ └── angular.json ├── .prettierrc ├── lerna.json ├── .gitignore ├── package.json ├── tsconfig.json ├── nx.json ├── typescript-clean-architecture.code-workspace └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.13.0 2 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/data/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/di/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/data/src/tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/presentation/src/di/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/presentation/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /packages/presentation/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 120 4 | } -------------------------------------------------------------------------------- /packages/data/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 120 4 | } -------------------------------------------------------------------------------- /packages/di/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 120 4 | } -------------------------------------------------------------------------------- /packages/presentation/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

2 | Hello World! 3 |

-------------------------------------------------------------------------------- /packages/presentation/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 120 4 | } -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "useNx": true, 4 | "version": "0.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 120, 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /packages/core/src/base/usecase.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Usecase { 2 | execute(...args: any[]): T; 3 | } 4 | -------------------------------------------------------------------------------- /packages/presentation/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /packages/presentation/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/build 3 | **/types 4 | 5 | # Dependency Cruiser Artifacts 6 | dependencygraph.svg 7 | *.log -------------------------------------------------------------------------------- /packages/core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | 4 | "exclude": ["node_modules", "src/tests"], 5 | } 6 | -------------------------------------------------------------------------------- /packages/data/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | 4 | "exclude": ["node_modules", "src/tests"], 5 | } 6 | -------------------------------------------------------------------------------- /packages/presentation/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aziznal/typescript-clean-architecture/HEAD/packages/presentation/src/favicon.ico -------------------------------------------------------------------------------- /packages/core/src/tests/example.test.ts: -------------------------------------------------------------------------------- 1 | describe("Example Test", () => { 2 | it("Adds two and two together", () => { 3 | expect(2 + 2).toBe(4); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/presentation/src/tests/example.test.ts: -------------------------------------------------------------------------------- 1 | describe('Example Test', () => { 2 | it('Adds two and two together', () => { 3 | expect(2 + 2).toBe(4); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/presentation/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { '^.+\\.ts?$': 'ts-jest' }, 3 | testEnvironment: 'node', 4 | testRegex: '.*\\.(test)?\\.(ts)$', 5 | moduleFileExtensions: ['ts', 'js'], 6 | }; -------------------------------------------------------------------------------- /packages/data/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { '^.+\\.ts?$': 'ts-jest' }, 3 | testEnvironment: 'node', 4 | testRegex: '.*\\.(test)?\\.(ts)$', 5 | moduleFileExtensions: ['ts', 'js'], 6 | }; -------------------------------------------------------------------------------- /packages/presentation/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { '^.+\\.ts?$': 'ts-jest' }, 3 | testEnvironment: 'node', 4 | testRegex: '.*\\.(test)?\\.(ts)$', 5 | moduleFileExtensions: ['ts', 'js'], 6 | }; -------------------------------------------------------------------------------- /packages/presentation/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | 10 | [*.ts] 11 | quote_type = single 12 | 13 | [*.md] 14 | max_line_length = off 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /packages/presentation/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'], 7 | }) 8 | export class AppComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit(): void {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/presentation/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HelloWorld 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/presentation/tsconfig.app.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/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-clean-architecture", 3 | "license": "ISC", 4 | "scripts": { 5 | "prestart": "npm install && lerna bootstrap", 6 | "start": "lerna run start", 7 | "test": "lerna run test", 8 | "build": "lerna run build", 9 | "graph": "nx graph" 10 | }, 11 | "devDependencies": { 12 | "lerna": "^5.1.8", 13 | "nx": "^14.4.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/presentation/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": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "sourceMap": true, 5 | 6 | "allowSyntheticDefaultImports": true, 7 | 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | 11 | "noImplicitAny": true, 12 | "noImplicitOverride": true, 13 | "noImplicitReturns": true, 14 | "forceConsistentCasingInFileNames": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "nx/tasks-runners/default", 5 | "options": { 6 | "cacheableOperations": ["build", "test"] 7 | } 8 | } 9 | }, 10 | "targetDefaults": { 11 | "build": { 12 | "dependsOn": ["^build"] 13 | }, 14 | "start": { 15 | "dependsOn": ["^build"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/presentation/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic() 13 | .bootstrapModule(AppModule) 14 | .catch((err) => console.error(err)); 15 | -------------------------------------------------------------------------------- /packages/presentation/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | 6 | import { AppComponent } from './app.component'; 7 | 8 | @NgModule({ 9 | declarations: [AppComponent], 10 | 11 | imports: [BrowserModule, CommonModule, FormsModule, ReactiveFormsModule], 12 | 13 | providers: [], 14 | 15 | bootstrap: [AppComponent], 16 | }) 17 | export class AppModule {} 18 | -------------------------------------------------------------------------------- /packages/presentation/.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 | -------------------------------------------------------------------------------- /typescript-clean-architecture.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "root", 5 | "path": "." 6 | }, 7 | { 8 | "name": "core", 9 | "path": "packages/core" 10 | }, 11 | { 12 | "name": "data", 13 | "path": "packages/data" 14 | }, 15 | { 16 | "name": "di", 17 | "path": "packages/di" 18 | }, 19 | { 20 | "name": "presentation", 21 | "path": "packages/presentation" 22 | } 23 | ], 24 | "settings": {} 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | 4 | "include": ["src/**/*.ts"], 5 | 6 | "exclude": ["node_modules"], 7 | 8 | "compilerOptions": { 9 | "baseUrl": ".", 10 | "rootDir": ".", 11 | 12 | "module": "ES2020", 13 | "target": "ES2020", 14 | "moduleResolution": "node", 15 | 16 | "outDir": "build/", 17 | 18 | "declaration": true, 19 | "declarationMap": true, 20 | "sourceMap": true, 21 | 22 | "plugins": [ 23 | { 24 | "name": "@rollup/plugin-typescript" 25 | } 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/data/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | 4 | "include": ["src/**/*.ts"], 5 | 6 | "exclude": ["node_modules"], 7 | 8 | "compilerOptions": { 9 | "baseUrl": ".", 10 | "rootDir": ".", 11 | 12 | "module": "ES2020", 13 | "target": "ES2020", 14 | "moduleResolution": "node", 15 | 16 | "outDir": "build/", 17 | 18 | "declaration": true, 19 | "declarationMap": true, 20 | "sourceMap": true, 21 | 22 | "plugins": [ 23 | { 24 | "name": "@rollup/plugin-typescript" 25 | } 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/presentation/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /packages/presentation/.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 | -------------------------------------------------------------------------------- /packages/presentation/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /packages/di/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | 4 | "include": ["src/**/*.ts"], 5 | 6 | "exclude": ["node_modules"], 7 | 8 | "compilerOptions": { 9 | "baseUrl": ".", 10 | "rootDir": ".", 11 | 12 | "module": "ES2020", 13 | "target": "ES2020", 14 | "moduleResolution": "node", 15 | 16 | "lib": ["ES6", "DOM"], 17 | 18 | "experimentalDecorators": true, 19 | "emitDecoratorMetadata": true, 20 | 21 | "declaration": true, 22 | "declarationMap": true, 23 | "sourceMap": true, 24 | 25 | "outDir": "build/", 26 | 27 | "plugins": [ 28 | { 29 | "name": "@rollup/plugin-typescript" 30 | } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/di/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "di", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "tsc && npm run rollup", 6 | "build:watch": "nodemon -e ts --exec \"npm run build\" --ignore build/** --ignore tests/**", 7 | "rollup": "rollup -c" 8 | }, 9 | "license": "ISC", 10 | "dependencies": { 11 | "core": "*", 12 | "data": "*" 13 | }, 14 | "devDependencies": { 15 | "@rollup/plugin-typescript": "^8.3.3", 16 | "@types/node": "^18.0.0", 17 | "typescript": "^4.7.4", 18 | "rollup": "^2.77.0", 19 | "rollup-plugin-dts": "4.2.2", 20 | "nodemon": "^2.0.19", 21 | "tslib": "^2.4.0" 22 | }, 23 | "main": "build/src/index.js", 24 | "types": "build/src/index.d.ts" 25 | } 26 | -------------------------------------------------------------------------------- /packages/presentation/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import dts from 'rollup-plugin-dts'; 3 | 4 | import tsConfig from './tsconfig.json'; 5 | 6 | const config = [ 7 | { 8 | input: "src/index.ts", 9 | output: [{ file: "build/src/index.js", sourcemap: true }], 10 | plugins: [ 11 | typescript( 12 | { 13 | sourceMap: tsConfig.compilerOptions.sourceMap 14 | } 15 | ) 16 | ] 17 | }, 18 | { 19 | input: 'build/src/index.d.ts', 20 | output: [{ file: "build/src/index.d.ts", "format": "es" }], 21 | plugins: [ 22 | dts( 23 | { 24 | compilerOptions: { 25 | baseUrl: tsConfig.compilerOptions.baseUrl 26 | } 27 | } 28 | ) 29 | ] 30 | }, 31 | ] 32 | 33 | export default config; -------------------------------------------------------------------------------- /packages/data/rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import dts from 'rollup-plugin-dts'; 3 | 4 | import tsConfig from './tsconfig.json'; 5 | 6 | const config = [ 7 | { 8 | input: "src/index.ts", 9 | output: [{ file: "build/src/index.js", sourcemap: true }], 10 | plugins: [ 11 | typescript( 12 | { 13 | sourceMap: tsConfig.compilerOptions.sourceMap, 14 | } 15 | ) 16 | ] 17 | }, 18 | { 19 | input: 'build/src/index.d.ts', 20 | output: [{ file: "build/src/index.d.ts", "format": "es" }], 21 | plugins: [ 22 | dts( 23 | { 24 | compilerOptions: { 25 | baseUrl: tsConfig.compilerOptions.baseUrl 26 | } 27 | } 28 | ) 29 | ] 30 | }, 31 | ] 32 | 33 | export default config; -------------------------------------------------------------------------------- /packages/di/rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import dts from 'rollup-plugin-dts'; 3 | 4 | import tsConfig from './tsconfig.json'; 5 | 6 | const config = [ 7 | { 8 | input: "src/index.ts", 9 | output: [{ file: "build/src/index.js", sourcemap: true }], 10 | plugins: [ 11 | typescript( 12 | { 13 | sourceMap: tsConfig.compilerOptions.sourceMap, 14 | } 15 | ) 16 | ] 17 | }, 18 | { 19 | input: 'build/src/index.d.ts', 20 | output: [{ file: "build/src/index.d.ts", "format": "es" }], 21 | plugins: [ 22 | dts( 23 | { 24 | compilerOptions: { 25 | baseUrl: tsConfig.compilerOptions.baseUrl 26 | } 27 | } 28 | ) 29 | ] 30 | }, 31 | ] 32 | 33 | export default config; -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "core", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "tsc --project tsconfig.build.json && npm run rollup", 7 | "build:watch": "nodemon -e ts --exec \"npm run build\" --ignore build/** --ignore tests/**", 8 | "rollup": "rollup -c", 9 | "test": "jest", 10 | "test:watch": "jest --watch" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@rollup/plugin-typescript": "^8.3.3", 16 | "@types/jest": "^28.1.6", 17 | "@types/node": "^18.0.0", 18 | "jest": "^28.1.3", 19 | "nodemon": "^2.0.19", 20 | "rollup": "^2.77.0", 21 | "rollup-plugin-dts": "^4.2.2", 22 | "ts-jest": "^28.0.7", 23 | "tslib": "^2.4.0", 24 | "typescript": "^4.7.4" 25 | }, 26 | "main": "build/src/index.js", 27 | "types": "build/src/index.d.ts" 28 | } 29 | -------------------------------------------------------------------------------- /packages/data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "tsc --project tsconfig.build.json && npm run rollup", 6 | "build:watch": "nodemon -e ts --exec \"npm run build\" --ignore build/** --ignore tests/**", 7 | "rollup": "rollup -c", 8 | "test": "jest", 9 | "test:watch": "jest --watch" 10 | }, 11 | "license": "ISC", 12 | "dependencies": { 13 | "core": "*" 14 | }, 15 | "devDependencies": { 16 | "@rollup/plugin-typescript": "^8.3.3", 17 | "@types/node": "^18.0.0", 18 | "@types/jest": "^28.1.6", 19 | "typescript": "^4.7.4", 20 | "nodemon": "^2.0.19", 21 | "rollup": "^2.77.0", 22 | "rollup-plugin-dts": "4.2.2", 23 | "ts-jest": "^28.0.7", 24 | "tslib": "^2.4.0", 25 | "jest": "^28.1.3" 26 | }, 27 | "main": "build/src/index.js", 28 | "types": "build/src/index.d.ts" 29 | } 30 | -------------------------------------------------------------------------------- /packages/presentation/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | }); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'presentation'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.componentInstance; 22 | expect(app.title).toEqual('presentation'); 23 | }); 24 | 25 | it('should render title', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.nativeElement as HTMLElement; 29 | expect(compiled.querySelector('.content span')?.textContent).toContain('presentation app is running!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/presentation/.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 | -------------------------------------------------------------------------------- /packages/presentation/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "include": ["src/**/*.ts"], 4 | 5 | "compileOnSave": false, 6 | "compilerOptions": { 7 | "baseUrl": "./", 8 | "outDir": "./dist/out-tsc", 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "noImplicitOverride": true, 12 | "noPropertyAccessFromIndexSignature": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "sourceMap": true, 16 | "declaration": false, 17 | "downlevelIteration": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "node", 20 | "importHelpers": true, 21 | "target": "es2020", 22 | "module": "es2020", 23 | "lib": ["es2020", "dom"], 24 | 25 | "allowSyntheticDefaultImports": true 26 | }, 27 | 28 | "angularCompilerOptions": { 29 | "enableI18nLegacyMessageIdFormat": false, 30 | "strictInjectionParameters": true, 31 | "strictInputAccessModifiers": true, 32 | "strictTemplates": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/presentation/README.md: -------------------------------------------------------------------------------- 1 | # HelloWorld 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.1. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /packages/presentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "presentation", 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": "jest", 10 | "test:watch": "jest --watch", 11 | "test:integration": "ng test" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^14.0.0", 16 | "@angular/common": "^14.0.0", 17 | "@angular/compiler": "^14.0.0", 18 | "@angular/core": "^14.0.0", 19 | "@angular/forms": "^14.0.0", 20 | "@angular/platform-browser": "^14.0.0", 21 | "@angular/platform-browser-dynamic": "^14.0.0", 22 | "@angular/router": "^14.0.0", 23 | "rxjs": "~7.5.0", 24 | "tslib": "^2.3.0", 25 | "zone.js": "~0.11.4", 26 | "core": "*", 27 | "di": "*" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "^14.0.1", 31 | "@angular/cli": "~14.0.1", 32 | "@angular/compiler-cli": "^14.0.0", 33 | "@types/jasmine": "~4.0.0", 34 | "jasmine-core": "~4.1.0", 35 | "karma": "~6.3.0", 36 | "karma-chrome-launcher": "~3.1.0", 37 | "karma-coverage": "~2.2.0", 38 | "karma-jasmine": "~5.0.0", 39 | "karma-jasmine-html-reporter": "~1.7.0", 40 | "typescript": "~4.7.2", 41 | "@types/jest": "^28.1.6", 42 | "jest": "^28.1.3", 43 | "ts-jest": "^28.0.7" 44 | }, 45 | "nx": { 46 | "targets": { 47 | "build": { 48 | "outputs": [ 49 | "{projectRoot}/dist" 50 | ] 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/presentation/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/presentation'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /packages/presentation/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /packages/presentation/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "presentation": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/presentation", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "inlineStyleLanguage": "scss", 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "src/styles.scss" 32 | ], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "budgets": [ 38 | { 39 | "type": "initial", 40 | "maximumWarning": "500kb", 41 | "maximumError": "1mb" 42 | }, 43 | { 44 | "type": "anyComponentStyle", 45 | "maximumWarning": "2kb", 46 | "maximumError": "4kb" 47 | } 48 | ], 49 | "fileReplacements": [ 50 | { 51 | "replace": "src/environments/environment.ts", 52 | "with": "src/environments/environment.prod.ts" 53 | } 54 | ], 55 | "outputHashing": "all" 56 | }, 57 | "development": { 58 | "buildOptimizer": false, 59 | "optimization": false, 60 | "vendorChunk": true, 61 | "extractLicenses": false, 62 | "sourceMap": true, 63 | "namedChunks": true 64 | } 65 | }, 66 | "defaultConfiguration": "production" 67 | }, 68 | "serve": { 69 | "builder": "@angular-devkit/build-angular:dev-server", 70 | "configurations": { 71 | "production": { 72 | "browserTarget": "presentation:build:production" 73 | }, 74 | "development": { 75 | "browserTarget": "presentation:build:development" 76 | } 77 | }, 78 | "defaultConfiguration": "development" 79 | }, 80 | "extract-i18n": { 81 | "builder": "@angular-devkit/build-angular:extract-i18n", 82 | "options": { 83 | "browserTarget": "presentation:build" 84 | } 85 | }, 86 | "test": { 87 | "builder": "@angular-devkit/build-angular:karma", 88 | "options": { 89 | "main": "src/test.ts", 90 | "polyfills": "src/polyfills.ts", 91 | "tsConfig": "tsconfig.spec.json", 92 | "karmaConfig": "karma.conf.js", 93 | "inlineStyleLanguage": "scss", 94 | "assets": [ 95 | "src/favicon.ico", 96 | "src/assets" 97 | ], 98 | "styles": [ 99 | "src/styles.scss" 100 | ], 101 | "scripts": [] 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-clean-architecture 2 | 3 | A typescript project template using clean architecture. 4 | 5 | You can use this template for any application you need, not just Web! 6 | 7 | # About 8 | 9 | ## Setting Up 10 | 11 | 1. Clone Repo 12 | 2. Within **root**, run `npm start`. This will install dependencies and setup lerna. 13 | 3. Done 14 | 15 | ## How to use 16 | 17 | You can read [this article](https://medium.com/@aziznal/27c7eb745ab4) explaining my idea of how this article can be used. 18 | 19 | Also, see the [counter-example](https://github.com/aziznal/typescript-clean-architecture/tree/counter-example) branch to see different layers in action together. 20 | 21 | The project structure is split into three main layers (core, data, presentation), as well as one auxiliary layer (di). 22 | 23 | A typical programming workflow for a new feature would look like this: 24 | 25 | - You start writing your app in **core** 26 | - You support **core** with a repository implemented in **data** that implements methods which use cases in **core** need 27 | - You create / update factories in **di** that create the **core** use cases / repos with their dependencies from **data** given to them 28 | - You setup the use of **core** use cases in **presentation** 29 | - You implement your feature in **presentation** 30 | 31 | Remember that **Presentation** knows ONLY Use Cases and Entities, NOT Repos! That's **Data**'s job, and it's hidden behind dependency injection. 32 | 33 | ## Useful commands to know 34 | 35 | - For each package (core, data, and di), you might want to run `npm run build:watch` while you're modifying that package. This'll make it 36 | rebuild for every save you make and other packages will be able to see those changes 37 | - Within root, `npx lerna run build` will re-build all your packages (that have changed since last build, otherwise it'll use cached version) 38 | - Within root, `npx lerna run build --scope=core` will re-build only core. You can substitute core for data, di, or presentation. 39 | - Within root, `npx lerna clean` will remove all node_modules folders in all packages including root. 40 | 41 | ## Extra 42 | 43 | VS Code workspaces are a life saver. Open each package in its own workspace and you'll have zero tsconfig or prettier issues etc. 44 | 45 | Sometimes, vs code (if that's what you're using) intellisense will not detect changes you made in another package. This is annoying as heck but closing 46 | and reopening vs code usually solves it. Sometimes, it just goes away on its own when a build command is ran. 47 | 48 | # Layers of the project 49 | 50 | ## Core 51 | 52 | Core is the layer where you can create your Entities, Use Cases, and Repo Interfaces. 53 | 54 | - **An Entity** is a model of an object in your application. It can be a User, a Course, or a Fruit. It's a Thing in your app that is core to how your app functions. 55 | 56 | - **A Use Case** is a way for you to use Entities to get something done. For example, a use case for a user would be login, sign up, add to favorites, send message to other user, etc.. 57 | 58 | Use Cases typically require a data source to call on so they can retrieve data as well as save it. We cover this using a Repository interface defined in **core** and its implementation can then be defined in another layer such as **data**. 59 | 60 | - **Repository Interfaces** are what use cases call upon when they need their data. They define a few methods (such as create, get, update, delete, etc.. but it doesn't have to look like a REST api) and use cases call on these methods as they need them. 61 | 62 | ## Data 63 | 64 | Data is the layer that supports core with data. Use cases in core, through a repo interface, will call the data layer to request some data. The data layer manages whether that data is from an API or a cache, etc. 65 | 66 | ## DI (dependency injection) 67 | This is the layer where **Core** and **Data** are associated together and Data implementations are provided to instantiations of Core objects as they are needed. Typically, you'll be instantiating **Core** use case implementations and providing them with an implementation of a repository from **Data**. 68 | 69 | Example: 70 | ```typescript 71 | /* in di/src/core.factory.ts */ 72 | import * as core from 'core'; 73 | import * as data from 'data'; 74 | 75 | class CoreFactory { 76 | userRepo: core.UserRepo; 77 | 78 | constructor(private otherDependencies: any[]) { 79 | this.userRepo = new data.UserRepoImpl(/* pass any required args here */); 80 | } 81 | 82 | createLoginUsecase(): core.LoginUsecase { 83 | return new core.LoginUsecaseImpl(this.userRepo) 84 | } 85 | 86 | createSignupUsecase(): core.SignupUsecase { 87 | return new core.LoginUsecaseImpl(this.userRepo) 88 | } 89 | } 90 | ``` 91 | 92 | You can use this core factory in your presentation's dependency injection solution like this (Angular example provided): 93 | 94 | ```typescript 95 | /* in app.module.ts */ 96 | import * as di from 'di'; 97 | 98 | const coreFactory = new di.CoreFactory(); 99 | 100 | @NgModule({ 101 | // ... 102 | providers: [ 103 | { 104 | provide: core.LoginUsecase, 105 | useFactory: () => coreFactory.createLoginUsecase() 106 | }, 107 | { 108 | provide: core.SignupUsecase, 109 | useFactory: () => coreFactory.createSignupUsecase() 110 | }, 111 | ], 112 | //... 113 | }) 114 | export class AppModule {} 115 | 116 | ``` 117 | 118 | Now you have access to your use cases through dependency injection! 119 | 120 | Note that the di layer and something like you app.module.ts file can become rather messy. This is normal. It's to be expected since they are the single point where everything is introduced together. There are a few things to do to make them tidier, such as isolating your providers array in app.module into its own file where you then import it into app.module. See [this example](https://github.com/aziznal/typescript-clean-architecture/blob/counter-example/packages/presentation/src/di/counter.ioc.ts) of a providers file and how it's used in the [app.module](https://github.com/aziznal/typescript-clean-architecture/blob/counter-example/packages/presentation/src/app/app.module.ts) file. It doesn't get easier! 121 | 122 | ## Presentation 123 | This is the layer where you write you UI code. You can do that using a web framework such as Angular or React, or using something else like Electron if you're making a desktop app. Heck, you can even make a console app if you wanted! 124 | 125 | ## Other Layers 126 | You can very easily add more layers to this architecture, depending on your need. For example, maybe you prefer to isolate caching into its own layer because you have a complicated caching mechanism, or perhaps you have multiple implementations of presentation such as one for web and one for desktop etc. 127 | --------------------------------------------------------------------------------