├── .editorconfig ├── .gitignore ├── App_Resources ├── Android │ ├── AndroidManifest.xml │ ├── app.gradle │ ├── drawable-hdpi │ │ ├── background.png │ │ ├── icon.png │ │ └── logo.png │ ├── drawable-ldpi │ │ ├── background.png │ │ ├── icon.png │ │ └── logo.png │ ├── drawable-mdpi │ │ ├── background.png │ │ ├── icon.png │ │ └── logo.png │ ├── drawable-nodpi │ │ └── splash_screen.xml │ ├── drawable-xhdpi │ │ ├── background.png │ │ ├── icon.png │ │ └── logo.png │ ├── drawable-xxhdpi │ │ ├── background.png │ │ ├── icon.png │ │ └── logo.png │ ├── drawable-xxxhdpi │ │ ├── background.png │ │ ├── icon.png │ │ └── logo.png │ ├── values-v21 │ │ ├── colors.xml │ │ └── styles.xml │ └── values │ │ ├── colors.xml │ │ └── styles.xml └── iOS │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-1024.png │ │ ├── icon-29.png │ │ ├── icon-29@2x.png │ │ ├── icon-29@3x.png │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ └── icon-83.5@2x.png │ ├── Contents.json │ ├── LaunchImage.launchimage │ │ ├── Contents.json │ │ ├── Default-1125h.png │ │ ├── Default-568h@2x.png │ │ ├── Default-667h@2x.png │ │ ├── Default-736h@3x.png │ │ ├── Default-Landscape-X.png │ │ ├── Default-Landscape.png │ │ ├── Default-Landscape@2x.png │ │ ├── Default-Landscape@3x.png │ │ ├── Default-Portrait.png │ │ ├── Default-Portrait@2x.png │ │ ├── Default.png │ │ └── Default@2x.png │ ├── LaunchScreen.AspectFill.imageset │ │ ├── Contents.json │ │ ├── LaunchScreen-AspectFill.png │ │ └── LaunchScreen-AspectFill@2x.png │ └── LaunchScreen.Center.imageset │ │ ├── Contents.json │ │ ├── LaunchScreen-Center.png │ │ └── LaunchScreen-Center@2x.png │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── build.xcconfig ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── ngsw-config.json ├── nsconfig.json ├── package-lock.json ├── package.json ├── src ├── app.css ├── app │ ├── app-routing.module.tns.ts │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.tns.html │ ├── app.component.tns.ts │ ├── app.component.ts │ ├── app.effects.spec.ts │ ├── app.effects.ts │ ├── app.module.tns.ts │ ├── app.module.ts │ ├── auth │ │ ├── actions │ │ │ └── auth.actions.ts │ │ ├── auth-routing.module.ts │ │ ├── auth.module.ts │ │ ├── components │ │ │ └── login │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.scss │ │ │ │ ├── login.component.spec.ts │ │ │ │ └── login.component.ts │ │ ├── containers │ │ │ └── login-page │ │ │ │ ├── login-page.component.html │ │ │ │ ├── login-page.component.scss │ │ │ │ ├── login-page.component.spec.ts │ │ │ │ └── login-page.component.ts │ │ ├── effects │ │ │ ├── auth.effects.spec.ts │ │ │ └── auth.effects.ts │ │ ├── models │ │ │ ├── login-credentials.ts │ │ │ ├── login-response.ts │ │ │ ├── user.spec.ts │ │ │ └── user.ts │ │ ├── reducers │ │ │ ├── auth.reducer.spec.ts │ │ │ ├── auth.reducer.ts │ │ │ └── index.ts │ │ └── services │ │ │ ├── api-auth.service.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── guards │ │ │ ├── auth-guard.service.spec.ts │ │ │ └── auth-guard.service.ts │ │ │ ├── interceptors │ │ │ ├── bearer-token-interceptor.ts │ │ │ └── redirect-unauthorized-interceptor.ts │ │ │ └── mock-auth.service.ts │ ├── auto-generated │ │ ├── auto-generated.component.css │ │ ├── auto-generated.component.html │ │ ├── auto-generated.component.tns.css │ │ ├── auto-generated.component.tns.html │ │ └── auto-generated.component.ts │ ├── core │ │ ├── core.module.ts │ │ ├── domain │ │ │ ├── messaging-service.ts │ │ │ ├── models │ │ │ │ ├── message.spec.ts │ │ │ │ └── message.ts │ │ │ ├── observable-use-case.spec.ts │ │ │ ├── observable-use-case.ts │ │ │ ├── single-usecase.ts │ │ │ └── usecase.ts │ │ └── presentation │ │ │ ├── concerns │ │ │ └── editable.ts │ │ │ ├── presenter.ts │ │ │ └── viewmodels │ │ │ └── view-model.ts │ ├── dashboard │ │ ├── containers │ │ │ └── dashboard-page │ │ │ │ ├── dashboard-page.component.html │ │ │ │ ├── dashboard-page.component.scss │ │ │ │ ├── dashboard-page.component.spec.ts │ │ │ │ └── dashboard-page.component.ts │ │ ├── dashboard-routing.module.ts │ │ └── dashboard.module.ts │ ├── material │ │ └── material.module.ts │ ├── reducers │ │ └── index.ts │ └── todo │ │ ├── data │ │ └── api │ │ │ ├── api-todo-service.spec.ts │ │ │ ├── api-todo-service.ts │ │ │ ├── mock-todo-service.spec.ts │ │ │ ├── mock-todo-service.ts │ │ │ ├── todo.service.spec.ts │ │ │ └── todo.service.ts │ │ ├── domain │ │ ├── models │ │ │ └── todo.ts │ │ ├── state │ │ │ ├── actions │ │ │ │ └── todo.actions.ts │ │ │ ├── effects │ │ │ │ ├── todo.effects.spec.ts │ │ │ │ └── todo.effects.ts │ │ │ └── reducers │ │ │ │ ├── index.ts │ │ │ │ ├── todo.reducer.spec.ts │ │ │ │ └── todo.reducer.ts │ │ └── usecases │ │ │ ├── add-todo.ts │ │ │ ├── load-todos.ts │ │ │ ├── remove-todo.ts │ │ │ └── toggle-todo.ts │ │ ├── presentation │ │ ├── components │ │ │ └── todo-item │ │ │ │ ├── todo-item.component.html │ │ │ │ ├── todo-item.component.scss │ │ │ │ ├── todo-item.component.spec.ts │ │ │ │ ├── todo-item.component.tns.html │ │ │ │ ├── todo-item.component.tns.scss │ │ │ │ └── todo-item.component.ts │ │ ├── containers │ │ │ └── todo-list │ │ │ │ ├── todo-list.component.html │ │ │ │ ├── todo-list.component.scss │ │ │ │ ├── todo-list.component.spec.ts │ │ │ │ ├── todo-list.component.tns.css │ │ │ │ ├── todo-list.component.tns.html │ │ │ │ └── todo-list.component.ts │ │ ├── directives │ │ │ └── InputAutofocus.ts │ │ ├── presenter │ │ │ ├── mappers │ │ │ │ ├── mapper.spec.ts │ │ │ │ ├── mapper.ts │ │ │ │ ├── todo-mapper.spec.ts │ │ │ │ └── todo-mapper.ts │ │ │ └── todo-presenter.service.ts │ │ └── viewmodels │ │ │ ├── todo-viewmodel.spec.ts │ │ │ └── todo-viewmodel.ts │ │ ├── todo-routing.module.ts │ │ ├── todo.common.ts │ │ ├── todo.module.tns.ts │ │ └── todo.module.ts ├── assets │ ├── .gitkeep │ └── icons │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ └── icon-96x96.png ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.tns.ts ├── main.ts ├── manifest.json ├── package.json ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json ├── tsconfig.tns.json ├── tslint.json └── webpack.config.js /.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 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NativeScriptnode_modules/ 2 | platforms/ 3 | hooks/ 4 | src/**/*.js 5 | # See http://help.github.com/ignore-files/ for more about ignoring files. 6 | 7 | # compiled output 8 | /dist 9 | /tmp 10 | /out-tsc 11 | 12 | # dependencies 13 | /node_modules 14 | 15 | # profiling files 16 | chrome-profiler-events.json 17 | speed-measure-plugin.json 18 | 19 | # IDEs and editors 20 | /.idea 21 | .project 22 | .classpath 23 | .c9/ 24 | *.launch 25 | .settings/ 26 | *.sublime-workspace 27 | 28 | # IDE - VSCode 29 | .vscode/* 30 | !.vscode/settings.json 31 | !.vscode/tasks.json 32 | !.vscode/launch.json 33 | !.vscode/extensions.json 34 | .history/* 35 | 36 | # misc 37 | /.sass-cache 38 | /connect.lock 39 | /coverage 40 | /libpeerconnection.log 41 | npm-debug.log 42 | yarn-error.log 43 | testem.log 44 | /typings 45 | 46 | # System Files 47 | .DS_Store 48 | Thumbs.db 49 | -------------------------------------------------------------------------------- /App_Resources/Android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /App_Resources/Android/app.gradle: -------------------------------------------------------------------------------- 1 | // Add your native dependencies here: 2 | 3 | // Uncomment to add recyclerview-v7 dependency 4 | //dependencies { 5 | // compile 'com.android.support:recyclerview-v7:+' 6 | //} 7 | 8 | android { 9 | defaultConfig { 10 | generatedDensities = [] 11 | applicationId = "org.nativescript.ngsample" 12 | 13 | //override supported platforms 14 | // ndk { 15 | // abiFilters.clear() 16 | // abiFilters "armeabi-v7a" 17 | // } 18 | 19 | } 20 | aaptOptions { 21 | additionalParameters "--no-version-vectors" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /App_Resources/Android/drawable-hdpi/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-hdpi/background.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-hdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-hdpi/logo.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-ldpi/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-ldpi/background.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-ldpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-ldpi/logo.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-mdpi/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-mdpi/background.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-mdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-mdpi/logo.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-nodpi/splash_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xhdpi/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xhdpi/background.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xhdpi/logo.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xxhdpi/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xxhdpi/background.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xxhdpi/logo.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xxxhdpi/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xxxhdpi/background.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /App_Resources/Android/drawable-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/Android/drawable-xxxhdpi/logo.png -------------------------------------------------------------------------------- /App_Resources/Android/values-v21/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3d5afe 4 | -------------------------------------------------------------------------------- /App_Resources/Android/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 14 | 15 | 16 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /App_Resources/Android/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F5F5F5 4 | #757575 5 | #33B5E5 6 | #272734 7 | -------------------------------------------------------------------------------- /App_Resources/Android/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | 21 | 22 | 23 | 31 | 32 | 34 | 35 | 36 | 42 | 43 | 45 | 46 | -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "icon-29.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "icon-29@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "icon-29@3x.png", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "icon-40@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "icon-40@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "icon-60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "icon-60@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "icon-29.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "29x29", 53 | "idiom" : "ipad", 54 | "filename" : "icon-29@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "icon-40.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "40x40", 65 | "idiom" : "ipad", 66 | "filename" : "icon-40@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "icon-76.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "76x76", 77 | "idiom" : "ipad", 78 | "filename" : "icon-76@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "83.5x83.5", 83 | "idiom" : "ipad", 84 | "filename" : "icon-83.5@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "1024x1024", 89 | "idiom" : "ios-marketing", 90 | "filename" : "icon-1024.png", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "2436h", 7 | "filename" : "Default-1125h.png", 8 | "minimum-system-version" : "11.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "orientation" : "landscape", 14 | "idiom" : "iphone", 15 | "extent" : "full-screen", 16 | "filename" : "Default-Landscape-X.png", 17 | "minimum-system-version" : "11.0", 18 | "subtype" : "2436h", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "extent" : "full-screen", 23 | "idiom" : "iphone", 24 | "subtype" : "736h", 25 | "filename" : "Default-736h@3x.png", 26 | "minimum-system-version" : "8.0", 27 | "orientation" : "portrait", 28 | "scale" : "3x" 29 | }, 30 | { 31 | "extent" : "full-screen", 32 | "idiom" : "iphone", 33 | "subtype" : "736h", 34 | "filename" : "Default-Landscape@3x.png", 35 | "minimum-system-version" : "8.0", 36 | "orientation" : "landscape", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "extent" : "full-screen", 41 | "idiom" : "iphone", 42 | "subtype" : "667h", 43 | "filename" : "Default-667h@2x.png", 44 | "minimum-system-version" : "8.0", 45 | "orientation" : "portrait", 46 | "scale" : "2x" 47 | }, 48 | { 49 | "orientation" : "portrait", 50 | "idiom" : "iphone", 51 | "filename" : "Default@2x.png", 52 | "extent" : "full-screen", 53 | "minimum-system-version" : "7.0", 54 | "scale" : "2x" 55 | }, 56 | { 57 | "extent" : "full-screen", 58 | "idiom" : "iphone", 59 | "subtype" : "retina4", 60 | "filename" : "Default-568h@2x.png", 61 | "minimum-system-version" : "7.0", 62 | "orientation" : "portrait", 63 | "scale" : "2x" 64 | }, 65 | { 66 | "orientation" : "portrait", 67 | "idiom" : "ipad", 68 | "filename" : "Default-Portrait.png", 69 | "extent" : "full-screen", 70 | "minimum-system-version" : "7.0", 71 | "scale" : "1x" 72 | }, 73 | { 74 | "orientation" : "landscape", 75 | "idiom" : "ipad", 76 | "filename" : "Default-Landscape.png", 77 | "extent" : "full-screen", 78 | "minimum-system-version" : "7.0", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "orientation" : "portrait", 83 | "idiom" : "ipad", 84 | "filename" : "Default-Portrait@2x.png", 85 | "extent" : "full-screen", 86 | "minimum-system-version" : "7.0", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "orientation" : "landscape", 91 | "idiom" : "ipad", 92 | "filename" : "Default-Landscape@2x.png", 93 | "extent" : "full-screen", 94 | "minimum-system-version" : "7.0", 95 | "scale" : "2x" 96 | }, 97 | { 98 | "orientation" : "portrait", 99 | "idiom" : "iphone", 100 | "filename" : "Default.png", 101 | "extent" : "full-screen", 102 | "scale" : "1x" 103 | }, 104 | { 105 | "orientation" : "portrait", 106 | "idiom" : "iphone", 107 | "filename" : "Default@2x.png", 108 | "extent" : "full-screen", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "orientation" : "portrait", 113 | "idiom" : "iphone", 114 | "filename" : "Default-568h@2x.png", 115 | "extent" : "full-screen", 116 | "subtype" : "retina4", 117 | "scale" : "2x" 118 | }, 119 | { 120 | "orientation" : "portrait", 121 | "idiom" : "ipad", 122 | "extent" : "to-status-bar", 123 | "scale" : "1x" 124 | }, 125 | { 126 | "orientation" : "portrait", 127 | "idiom" : "ipad", 128 | "filename" : "Default-Portrait.png", 129 | "extent" : "full-screen", 130 | "scale" : "1x" 131 | }, 132 | { 133 | "orientation" : "landscape", 134 | "idiom" : "ipad", 135 | "extent" : "to-status-bar", 136 | "scale" : "1x" 137 | }, 138 | { 139 | "orientation" : "landscape", 140 | "idiom" : "ipad", 141 | "filename" : "Default-Landscape.png", 142 | "extent" : "full-screen", 143 | "scale" : "1x" 144 | }, 145 | { 146 | "orientation" : "portrait", 147 | "idiom" : "ipad", 148 | "extent" : "to-status-bar", 149 | "scale" : "2x" 150 | }, 151 | { 152 | "orientation" : "portrait", 153 | "idiom" : "ipad", 154 | "filename" : "Default-Portrait@2x.png", 155 | "extent" : "full-screen", 156 | "scale" : "2x" 157 | }, 158 | { 159 | "orientation" : "landscape", 160 | "idiom" : "ipad", 161 | "extent" : "to-status-bar", 162 | "scale" : "2x" 163 | }, 164 | { 165 | "orientation" : "landscape", 166 | "idiom" : "ipad", 167 | "filename" : "Default-Landscape@2x.png", 168 | "extent" : "full-screen", 169 | "scale" : "2x" 170 | } 171 | ], 172 | "info" : { 173 | "version" : 1, 174 | "author" : "xcode" 175 | } 176 | } -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchScreen-AspectFill.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchScreen-AspectFill@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchScreen-Center.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchScreen-Center@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png -------------------------------------------------------------------------------- /App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png -------------------------------------------------------------------------------- /App_Resources/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiresFullScreen 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /App_Resources/iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /App_Resources/iOS/build.xcconfig: -------------------------------------------------------------------------------- 1 | // You can add custom settings here 2 | // for example you can uncomment the following line to force distribution code signing 3 | // CODE_SIGN_IDENTITY = iPhone Distribution 4 | // To build for device with Xcode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html 5 | // DEVELOPMENT_TEAM = YOUR_TEAM_ID; 6 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 7 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Cleaner Architecture 2 | ## What is it for ? 3 | Note : I used this repo to learn Angular/Ngrx and experiment different architecture concepts, this is probably badly written, not very functionnal and should not be used as a reference. If anyone ever submit a pull request, I will gladly look at it. 4 | 5 | This is my attempt at implementing Robert C. Martin (Uncle Bob) Clean Architecture guidelines in Angular 7. 6 | This project is using (among others) : 7 | - Angular's dependancy injection system 8 | - Ngrx observable store 9 | - Angular Material 10 | - Angular FlexLayout 11 | - Service Worker 12 | 13 | ###Featured modules: 14 | ####Auth : 15 | - Provides the app with a simple JWT authentication for Strapi.io API backend. 16 | - Login page and route 17 | - Login component 18 | 19 | ####Todo : 20 | 21 | This module is structured in 3 main layers : 22 | - Presentation : Angular containers and components that can only communicate with a facade (Sandbox) 23 | - Domain : Business logic and executable Usecase class that can dispatch actions and forward state changes to the presentation layer. 24 | - Data : Angular services for managing api requests, Ngrx store to maintain application state 25 | 26 | ##Roadmap 27 | 28 | - Make distinct Model classes for each layer with corresponding Mapper classes. 29 | - Implement repository pattern to decouple Domain Usecases from Ngrx store 30 | - Refactor Auth module to take advantage of 3 layer architecture. 31 | - Make a fully functional Todo list. 32 | - Try to use Data and Domain layers to make a Mobile app (Ionic, Nativescript etc). 33 | - Create angular-cli schematics to reduce boilerplate code. 34 | - Write tests. 35 | - Creation of a Layout module to manage layout state. 36 | - A lot more to come ! 37 | 38 | ## Development server 39 | 40 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 41 | 42 | ## Code scaffolding 43 | 44 | 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`. 45 | 46 | ## Build 47 | 48 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 49 | 50 | ## Running unit tests 51 | 52 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 53 | 54 | ## Running end-to-end tests 55 | 56 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 57 | 58 | ## Further help 59 | 60 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 61 | 62 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.1. 63 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "Ngrx": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@ngrx/schematics:component": { 13 | "styleext": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/Ngrx", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "src/tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets", 28 | "src/manifest.json" 29 | ], 30 | "styles": [ 31 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 32 | "src/styles.scss" 33 | ], 34 | "scripts": [] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "fileReplacements": [ 39 | { 40 | "replace": "src/environments/environment.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "extractCss": true, 48 | "namedChunks": false, 49 | "aot": true, 50 | "extractLicenses": true, 51 | "vendorChunk": false, 52 | "buildOptimizer": true, 53 | "budgets": [ 54 | { 55 | "type": "initial", 56 | "maximumWarning": "2mb", 57 | "maximumError": "5mb" 58 | } 59 | ], 60 | "serviceWorker": true 61 | } 62 | } 63 | }, 64 | "serve": { 65 | "builder": "@angular-devkit/build-angular:dev-server", 66 | "options": { 67 | "browserTarget": "Ngrx:build" 68 | }, 69 | "configurations": { 70 | "production": { 71 | "browserTarget": "Ngrx:build:production" 72 | } 73 | } 74 | }, 75 | "extract-i18n": { 76 | "builder": "@angular-devkit/build-angular:extract-i18n", 77 | "options": { 78 | "browserTarget": "Ngrx:build" 79 | } 80 | }, 81 | "test": { 82 | "builder": "@angular-devkit/build-angular:karma", 83 | "options": { 84 | "main": "src/test.ts", 85 | "polyfills": "src/polyfills.ts", 86 | "tsConfig": "src/tsconfig.spec.json", 87 | "karmaConfig": "src/karma.conf.js", 88 | "styles": [ 89 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 90 | "src/styles.scss" 91 | ], 92 | "scripts": [], 93 | "assets": [ 94 | "src/favicon.ico", 95 | "src/assets", 96 | "src/manifest.json" 97 | ] 98 | } 99 | }, 100 | "lint": { 101 | "builder": "@angular-devkit/build-angular:tslint", 102 | "options": { 103 | "tsConfig": [ 104 | "src/tsconfig.app.json", 105 | "src/tsconfig.spec.json" 106 | ], 107 | "exclude": [ 108 | "**/node_modules/**" 109 | ] 110 | } 111 | } 112 | } 113 | }, 114 | "Ngrx-e2e": { 115 | "root": "e2e/", 116 | "projectType": "application", 117 | "prefix": "", 118 | "architect": { 119 | "e2e": { 120 | "builder": "@angular-devkit/build-angular:protractor", 121 | "options": { 122 | "protractorConfig": "e2e/protractor.conf.js", 123 | "devServerTarget": "Ngrx:serve" 124 | }, 125 | "configurations": { 126 | "production": { 127 | "devServerTarget": "Ngrx:serve:production" 128 | } 129 | } 130 | }, 131 | "lint": { 132 | "builder": "@angular-devkit/build-angular:tslint", 133 | "options": { 134 | "tsConfig": "e2e/tsconfig.e2e.json", 135 | "exclude": [ 136 | "**/node_modules/**" 137 | ] 138 | } 139 | } 140 | } 141 | } 142 | }, 143 | "defaultProject": "Ngrx", 144 | "cli": { 145 | "defaultCollection": "@nativescript/schematics" 146 | } 147 | } -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getTitleText()).toEqual('Welcome to Ngrx!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "/index.html", 3 | "assetGroups": [ 4 | { 5 | "name": "app", 6 | "installMode": "prefetch", 7 | "resources": { 8 | "files": [ 9 | "/favicon.ico", 10 | "/index.html", 11 | "/*.css", 12 | "/*.js" 13 | ] 14 | } 15 | }, { 16 | "name": "assets", 17 | "installMode": "lazy", 18 | "updateMode": "prefetch", 19 | "resources": { 20 | "files": [ 21 | "/assets/**", 22 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 23 | ] 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /nsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "appResourcesPath": "App_Resources", 3 | "appPath": "src", 4 | "nsext": ".tns", 5 | "webext": "", 6 | "shared": true 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngrx", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "android": "tns run android --bundle", 6 | "ios": "tns run ios --bundle", 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "~7.2.0", 17 | "@angular/cdk": "github:angular/cdk-builds", 18 | "@angular/common": "~7.2.0", 19 | "@angular/compiler": "~7.2.0", 20 | "@angular/core": "~7.2.0", 21 | "@angular/flex-layout": "github:angular/flex-layout-builds", 22 | "@angular/forms": "~7.2.0", 23 | "@angular/material": "^7.2.1", 24 | "@angular/platform-browser": "~7.2.0", 25 | "@angular/platform-browser-dynamic": "~7.2.0", 26 | "@angular/pwa": "^0.12.2", 27 | "@angular/router": "~7.2.0", 28 | "@angular/service-worker": "~7.2.0", 29 | "@nativescript/schematics": "^0.5.0", 30 | "@ngrx/effects": "^7.0.0", 31 | "@ngrx/entity": "^7.1.0", 32 | "@ngrx/store": "^7.0.0", 33 | "@ngrx/store-devtools": "^7.0.0", 34 | "core-js": "^2.5.4", 35 | "hammerjs": "^2.0.8", 36 | "nativescript-angular": "~7.1.0", 37 | "nativescript-localstorage": "^2.0.0", 38 | "nativescript-theme-core": "~1.0.4", 39 | "ngrx-store-localstorage": "^6.0.0", 40 | "reflect-metadata": "~0.1.8", 41 | "rxjs": "~6.3.3", 42 | "tns-core-modules": "~5.1.1", 43 | "tslib": "^1.9.0", 44 | "zone.js": "~0.8.26" 45 | }, 46 | "devDependencies": { 47 | "nativescript-dev-webpack": "^0.18.0", 48 | "@nativescript/schematics": "~0.4.0", 49 | "@angular-devkit/build-angular": "~0.12.0", 50 | "@angular/cli": "~7.2.1", 51 | "@angular/compiler-cli": "~7.2.0", 52 | "@angular/language-service": "~7.2.0", 53 | "@ngrx/schematics": "^7.0.0", 54 | "@types/jasmine": "~2.8.8", 55 | "@types/jasminewd2": "~2.0.3", 56 | "@types/node": "~8.9.4", 57 | "codelyzer": "~4.5.0", 58 | "jasmine-core": "~2.99.1", 59 | "jasmine-spec-reporter": "~4.2.1", 60 | "karma": "~3.1.1", 61 | "karma-chrome-launcher": "~2.2.0", 62 | "karma-coverage-istanbul-reporter": "~2.0.1", 63 | "karma-jasmine": "~1.1.2", 64 | "karma-jasmine-html-reporter": "^0.2.2", 65 | "protractor": "~5.4.0", 66 | "ts-node": "~7.0.0", 67 | "tslint": "~5.11.0", 68 | "typescript": "~3.2.2" 69 | }, 70 | "nativescript": { 71 | "id": "org.nativescript.ngsample", 72 | "tns-android": { 73 | "version": "5.0.0" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | /* 2 | In NativeScript, the app.css file is where you place CSS rules that 3 | you would like to apply to your entire application. Check out 4 | http://docs.nativescript.org/ui/styling for a full list of the CSS 5 | selectors and properties you can use to style UI components. 6 | 7 | /* 8 | In many cases you may want to use the NativeScript core theme instead 9 | of writing your own CSS rules. For a full list of class names in the theme 10 | refer to http://docs.nativescript.org/ui/theme. 11 | */ 12 | @import '~nativescript-theme-core/css/core.light.css'; 13 | -------------------------------------------------------------------------------- /src/app/app-routing.module.tns.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {NativeScriptRouterModule} from 'nativescript-angular/router'; 3 | import {Routes} from '@angular/router'; 4 | 5 | import {TodoListComponent} from './todo/presentation/containers/todo-list/todo-list.component'; 6 | 7 | export const routes: Routes = [ 8 | { 9 | path: '', 10 | redirectTo: 'todo', 11 | pathMatch: 'full', 12 | }, 13 | { 14 | path: 'todo', 15 | component: TodoListComponent, 16 | } 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [NativeScriptRouterModule.forRoot(routes)], 21 | exports: [NativeScriptRouterModule] 22 | }) 23 | export class AppRoutingModule { 24 | } 25 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {Routes, RouterModule} from '@angular/router'; 3 | import {AuthGuardService} from './auth/services/guards/auth-guard.service'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: 'dashboard', 8 | loadChildren: './dashboard/dashboard.module#DashboardModule', 9 | canActivate: [AuthGuardService] 10 | }, 11 | { 12 | path: 'todo', 13 | loadChildren: './todo/todo.module#TodoModule', 14 | canActivate: [AuthGuardService] 15 | }, 16 | { 17 | path: '', 18 | redirectTo: '', 19 | pathMatch: 'full' 20 | }, 21 | ]; 22 | 23 | @NgModule({ 24 | imports: [RouterModule.forRoot(routes)], 25 | exports: [RouterModule] 26 | }) 27 | export class AppRoutingModule { 28 | } 29 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 80 | Menu 81 | 82 | Dashboard 83 | Todo 84 | 85 | 86 | 87 | 88 | 89 | 90 | 98 | 99 | 100 | FlexPlayground 101 | 109 | 110 | 111 | 112 |
113 | 114 |
115 |
116 |
117 |
118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .sidenav-container { 3 | height: 100%; 4 | } 5 | 6 | .sidenav { 7 | width: 200px; 8 | } 9 | 10 | .sidenav .mat-toolbar { 11 | background: inherit; 12 | } 13 | 14 | .mat-toolbar.mat-primary { 15 | position: sticky; 16 | top: 0; 17 | z-index: 1; 18 | } 19 | 20 | .content { 21 | margin: 16px; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | 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.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'Ngrx'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('Ngrx'); 23 | }); 24 | 25 | it('should render title in a h1 tag', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to Ngrx!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/app.component.tns.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/app.component.tns.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {MessagingService} from './core/domain/messaging-service'; 3 | import * as dialogs from 'tns-core-modules/ui/dialogs'; 4 | import {Store} from '@ngrx/store'; 5 | import {MatSnackBar} from '@angular/material'; 6 | import * as fromAuth from './auth/reducers'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './app.component.html', 11 | }) 12 | 13 | export class AppComponent { 14 | 15 | 16 | constructor(readonly store: Store, messagingService: MessagingService) { 17 | messagingService.getMessages().subscribe(message => { 18 | console.log(message.message); 19 | }); 20 | } 21 | 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Store} from '@ngrx/store'; 3 | import {Logout} from './auth/actions/auth.actions'; 4 | import {Observable} from 'rxjs'; 5 | import * as fromAuth from './auth/reducers'; 6 | import {MessagingService} from './core/domain/messaging-service'; 7 | import {MatSnackBar} from '@angular/material'; 8 | import {ofType} from '@ngrx/effects'; 9 | import {Message, MessageTypes} from './core/domain/models/message'; 10 | import {filter, map} from 'rxjs/operators'; 11 | import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout'; 12 | 13 | @Component({ 14 | selector: 'app-root', 15 | templateUrl: './app.component.html', 16 | styleUrls: ['./app.component.scss'] 17 | }) 18 | export class AppComponent { 19 | 20 | constructor(readonly store: Store, 21 | messagingService: MessagingService, 22 | snackbar: MatSnackBar, 23 | private breakpointObserver: BreakpointObserver) { 24 | store.select(fromAuth.selectAuthenticated).subscribe(authenticated => this.authenticated = authenticated); 25 | messagingService.getMessages().subscribe(message => { 26 | console.log(message.message); 27 | snackbar.open(message.message, '', {duration: 2500}); 28 | }); 29 | } 30 | title = 'TodoManager'; 31 | 32 | authenticated: boolean; 33 | 34 | isHandset$: Observable = this.breakpointObserver.observe(Breakpoints.Handset) 35 | .pipe( 36 | map(result => result.matches) 37 | ); 38 | 39 | logout() { 40 | this.store.dispatch(new Logout()); 41 | } 42 | 43 | login() { 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/app/app.effects.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { provideMockActions } from '@ngrx/effects/testing'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { AppEffects } from './app.effects'; 6 | 7 | describe('AppEffects', () => { 8 | let actions$: Observable; 9 | let effects: AppEffects; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | AppEffects, 15 | provideMockActions(() => actions$) 16 | ] 17 | }); 18 | 19 | effects = TestBed.get(AppEffects); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(effects).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/app.effects.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Actions, Effect, ofType} from '@ngrx/effects'; 3 | import {Observable} from 'rxjs'; 4 | import {tap} from 'rxjs/operators'; 5 | import {MessagingService} from './core/domain/messaging-service'; 6 | import {Action} from '@ngrx/store'; 7 | 8 | 9 | @Injectable() 10 | export class AppEffects { 11 | 12 | constructor(private actions$: Actions, private messagingService: MessagingService) { 13 | } 14 | 15 | @Effect({dispatch: false}) 16 | actionDebug$: Observable = this.actions$.pipe( 17 | tap(action => { 18 | this.messagingService.post({message: action.type}); 19 | }) 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/app.module.tns.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; 2 | import { NativeScriptModule } from 'nativescript-angular/nativescript.module'; 3 | 4 | import { AppRoutingModule } from './app-routing.module.tns'; 5 | 6 | import { AutoGeneratedComponent } from './auto-generated/auto-generated.component'; 7 | import {TodoListComponent} from './todo/presentation/containers/todo-list/todo-list.component'; 8 | import {TodoModule} from './todo/todo.module.tns'; 9 | import {StoreModule} from '@ngrx/store'; 10 | import {metaReducers, reducers} from './reducers'; 11 | import {EffectsModule} from '@ngrx/effects'; 12 | import {AppEffects} from './app.effects'; 13 | import 'nativescript-localstorage' 14 | import {NativeScriptHttpClientModule} from 'nativescript-angular/http-client'; 15 | import {AppComponent} from './app.component.tns'; 16 | 17 | // Uncomment and add to NgModule imports if you need to use two-way binding 18 | // import { NativeScriptFormsModule } from 'nativescript-angular/forms'; 19 | 20 | // Uncomment and add to NgModule imports if you need to use the HTTP wrapper 21 | // import { NativeScriptHttpClientModule } from 'nativescript-angular/http-client'; 22 | 23 | @NgModule({ 24 | declarations: [ 25 | AppComponent, 26 | AutoGeneratedComponent 27 | ], 28 | imports: [ 29 | NativeScriptModule, 30 | StoreModule.forRoot(reducers, {metaReducers}), 31 | EffectsModule.forRoot([AppEffects]), 32 | AppRoutingModule, 33 | NativeScriptHttpClientModule, 34 | TodoModule 35 | ], 36 | providers: [], 37 | bootstrap: [AppComponent], 38 | schemas: [NO_ERRORS_SCHEMA] 39 | }) 40 | /* 41 | Pass your application module to the bootstrapModule function located in main.ts to start your app 42 | */ 43 | export class AppModule { } 44 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {BrowserModule} from '@angular/platform-browser'; 2 | import {NgModule} from '@angular/core'; 3 | 4 | import {AppComponent} from './app.component'; 5 | 6 | import {StoreModule} from '@ngrx/store'; 7 | import {EffectsModule} from '@ngrx/effects'; 8 | import {reducers, metaReducers} from './reducers'; 9 | 10 | import {StoreDevtoolsModule} from '@ngrx/store-devtools'; 11 | import {environment} from '../environments/environment'; 12 | 13 | import {AppEffects} from './app.effects'; 14 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 15 | import {FlexLayoutModule} from '@angular/flex-layout'; 16 | import {MaterialModule} from './material/material.module'; 17 | 18 | import {AppRoutingModule} from './app-routing.module'; 19 | import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; 20 | import {AuthModule} from './auth/auth.module'; 21 | import {BearerTokenInterceptor} from './auth/services/interceptors/bearer-token-interceptor'; 22 | import {RedirectUnauthorizedInterceptor} from './auth/services/interceptors/redirect-unauthorized-interceptor'; 23 | import {CoreModule} from './core/core.module'; 24 | import {TodoModule} from './todo/todo.module'; 25 | import { ServiceWorkerModule } from '@angular/service-worker'; 26 | import { AutoGeneratedComponent } from './auto-generated/auto-generated.component'; 27 | 28 | @NgModule({ 29 | declarations: [ 30 | AppComponent, 31 | AutoGeneratedComponent 32 | ], 33 | imports: [ 34 | BrowserModule, 35 | AppRoutingModule, 36 | StoreModule.forRoot(reducers, {metaReducers}), 37 | !environment.production ? StoreDevtoolsModule.instrument() : [], 38 | EffectsModule.forRoot([AppEffects]), 39 | BrowserAnimationsModule, 40 | FlexLayoutModule, 41 | MaterialModule, 42 | CoreModule, 43 | HttpClientModule, 44 | AuthModule, 45 | TodoModule, 46 | ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }) 47 | ], 48 | providers: [ 49 | {provide: HTTP_INTERCEPTORS, useClass: BearerTokenInterceptor, multi: true}, 50 | {provide: HTTP_INTERCEPTORS, useClass: RedirectUnauthorizedInterceptor, multi: true} 51 | ], 52 | bootstrap: [AppComponent] 53 | }) 54 | export class AppModule { 55 | } 56 | -------------------------------------------------------------------------------- /src/app/auth/actions/auth.actions.ts: -------------------------------------------------------------------------------- 1 | import {Action} from '@ngrx/store'; 2 | import {User} from '../models/user'; 3 | import {LoginCredentials} from '../models/login-credentials'; 4 | import {LoginResponse} from '../models/login-response'; 5 | 6 | export enum LoginActionTypes { 7 | Login = '[Login Component] Login User', 8 | Logout = '[NavBar] Logout User', 9 | LoginSuccess = '[Auth API] Login Success', 10 | LoginFailed = '[Auth API] Login Failure', 11 | LogoutRedirect = '[Logout Action] Redirect to login', 12 | DashboardRedirect = '[LoginSuccess Action] Redirect to dashboard', 13 | AuthGuardRedirect = '[AuthGuard Service] Redirect to login', 14 | } 15 | 16 | export class Login implements Action { 17 | readonly type = LoginActionTypes.Login; 18 | 19 | constructor(readonly payload: LoginCredentials) { 20 | } 21 | } 22 | 23 | export class LoginSuccess implements Action { 24 | readonly type = LoginActionTypes.LoginSuccess; 25 | 26 | constructor(readonly payload: LoginResponse) { 27 | } 28 | } 29 | 30 | export class LoginFailed implements Action { 31 | readonly type = LoginActionTypes.LoginFailed; 32 | 33 | constructor(readonly payload: string) { 34 | } 35 | } 36 | 37 | export class Logout implements Action { 38 | readonly type = LoginActionTypes.Logout; 39 | } 40 | 41 | export class LoginRedirect implements Action { 42 | readonly type = LoginActionTypes.LogoutRedirect; 43 | } 44 | 45 | export class DashboardRedirect implements Action { 46 | readonly type = LoginActionTypes.DashboardRedirect; 47 | } 48 | 49 | export class AuthGuardRedirect implements Action { 50 | readonly type = LoginActionTypes.AuthGuardRedirect; 51 | } 52 | 53 | export type AuthActions = Login | Logout | LoginSuccess | LoginFailed | LoginRedirect | DashboardRedirect | AuthGuardRedirect; 54 | -------------------------------------------------------------------------------- /src/app/auth/auth-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import {LoginPageComponent} from './containers/login-page/login-page.component'; 4 | 5 | const routes: Routes = [{path: 'login', component: LoginPageComponent}]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class AuthRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule, Optional, SkipSelf} from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import {ActionReducer, StoreModule} from '@ngrx/store'; 4 | import * as fromAuth from './reducers/auth.reducer'; 5 | import { EffectsModule } from '@ngrx/effects'; 6 | import { AuthEffects } from './effects/auth.effects'; 7 | import { AuthRoutingModule} from './auth-routing.module'; 8 | import { LoginPageComponent } from './containers/login-page/login-page.component'; 9 | import { MaterialModule } from '../material/material.module'; 10 | import { ReactiveFormsModule} from '@angular/forms'; 11 | import { LoginComponent } from '../auth/components/login/login.component'; 12 | import {AuthGuardService} from './services/guards/auth-guard.service'; 13 | import {AuthService} from './services/auth.service'; 14 | import {HttpClient} from '@angular/common/http'; 15 | import {MockAuthService} from './services/mock-auth.service'; 16 | import {ApiAuthService} from './services/api-auth.service'; 17 | import {FlexLayoutModule} from '@angular/flex-layout'; 18 | 19 | @NgModule({ 20 | declarations: [LoginPageComponent, LoginComponent], 21 | imports: [ 22 | CommonModule, 23 | AuthRoutingModule, 24 | StoreModule.forFeature('auth', fromAuth.reducer), 25 | EffectsModule.forFeature([AuthEffects]), 26 | MaterialModule, 27 | ReactiveFormsModule, 28 | FlexLayoutModule 29 | ], 30 | exports: [LoginPageComponent], 31 | providers: [ 32 | { provide: AuthService, useClass: MockAuthService, deps: [HttpClient]} 33 | ] 34 | }) 35 | 36 | export class AuthModule { 37 | constructor (@Optional() @SkipSelf() parentModule: AuthModule) { 38 | if (parentModule) { 39 | throw new Error( 40 | 'AuthModule is already loaded. Import it in the AppModule only'); 41 | } 42 | } 43 | static forRoot(): ModuleWithProviders { 44 | return { 45 | ngModule: AuthModule, 46 | providers: [AuthGuardService], 47 | }; 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/app/auth/components/login/login.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Login 4 |
5 | 6 | 7 | 8 | Username is required 9 | 10 | 11 | 12 | Password is required 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/app/auth/components/login/login.component.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/auth/components/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/auth/components/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, EventEmitter, Input, Output } from '@angular/core'; 2 | import {FormControl, FormGroup, Validators} from '@angular/forms'; 3 | import {LoginCredentials} from '../../models/login-credentials'; 4 | 5 | @Component({ 6 | selector: 'app-login', 7 | templateUrl: './login.component.html', 8 | styleUrls: ['./login.component.scss'] 9 | }) 10 | export class LoginComponent implements OnInit { 11 | 12 | @Output() login = new EventEmitter(); 13 | @Input() loading; 14 | 15 | constructor() { } 16 | 17 | loginForm = new FormGroup({ 18 | username: new FormControl('', Validators.required), 19 | password: new FormControl('', Validators.required), 20 | }); 21 | 22 | username() { 23 | return this.loginForm.get('username'); 24 | } 25 | 26 | password() { 27 | return this.loginForm.get('password'); 28 | } 29 | 30 | ngOnInit() { 31 | 32 | } 33 | 34 | authenticate() { 35 | const credentials = {username: this.username().value, password: this.password().value}; 36 | this.login.emit(credentials); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/app/auth/containers/login-page/login-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/auth/containers/login-page/login-page.component.scss: -------------------------------------------------------------------------------- 1 | .row-height { 2 | height: 100vh ; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/auth/containers/login-page/login-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginPageComponent } from './login-page.component'; 4 | import { Store, StoreModule } from '@ngrx/store'; 5 | 6 | describe('LoginPageComponent', () => { 7 | let component: LoginPageComponent; 8 | let fixture: ComponentFixture; 9 | let store: Store; 10 | 11 | beforeEach(async() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ StoreModule.forRoot({}) ], 14 | declarations: [ LoginPageComponent ] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(LoginPageComponent); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/auth/containers/login-page/login-page.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Store} from '@ngrx/store'; 3 | import {MatSnackBar} from '@angular/material'; 4 | import {Login} from '../../actions/auth.actions'; 5 | import {from, Observable} from 'rxjs'; 6 | import {LoginCredentials} from '../../models/login-credentials'; 7 | import * as fromAuth from '../../reducers'; 8 | 9 | 10 | @Component({ 11 | selector: 'app-login-page', 12 | templateUrl: './login-page.component.html', 13 | styleUrls: ['./login-page.component.scss'] 14 | }) 15 | 16 | export class LoginPageComponent implements OnInit { 17 | 18 | loading$: Observable; 19 | 20 | constructor(private store: Store, public snackBar: MatSnackBar) { 21 | this.loading$ = store.select(fromAuth.selectLoading); 22 | } 23 | 24 | ngOnInit() { 25 | // this.loginForm.valueChanges.subscribe(value => console.warn(value)); 26 | 27 | } 28 | 29 | authenticate(credentials: LoginCredentials) { 30 | this.store.dispatch(new Login(credentials)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/auth/effects/auth.effects.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { provideMockActions } from '@ngrx/effects/testing'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { AuthEffects } from './auth.effects'; 6 | 7 | describe('AuthEffects', () => { 8 | let actions$: Observable; 9 | let effects: AuthEffects; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | AuthEffects, 15 | provideMockActions(() => actions$) 16 | ] 17 | }); 18 | 19 | effects = TestBed.get(AuthEffects); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(effects).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/auth/effects/auth.effects.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Actions, Effect, ofType} from '@ngrx/effects'; 3 | import { 4 | AuthGuardRedirect, 5 | DashboardRedirect, 6 | Login, 7 | LoginActionTypes, 8 | LoginFailed, 9 | LoginRedirect, 10 | LoginSuccess, 11 | Logout 12 | } from '../actions/auth.actions'; 13 | import {Observable, of} from 'rxjs'; 14 | import {Action} from '@ngrx/store'; 15 | import {catchError, map, mapTo, switchMap, tap, throttleTime} from 'rxjs/operators'; 16 | import {Router} from '@angular/router'; 17 | import {HttpErrorResponse} from '@angular/common/http'; 18 | import {AuthService} from '../services/auth.service'; 19 | 20 | export const THROTTLE_TIME = 1500; 21 | 22 | @Injectable() 23 | export class AuthEffects { 24 | 25 | @Effect() 26 | login$: Observable = this.actions$.pipe( 27 | ofType(LoginActionTypes.Login), 28 | throttleTime(THROTTLE_TIME), 29 | map((action: Login) => action.payload), 30 | switchMap(payload => { 31 | return this.authService.authenticate(payload).pipe( 32 | map(authResponse => { 33 | return new LoginSuccess(authResponse); 34 | }), 35 | catchError((error: HttpErrorResponse) => { 36 | return of(new LoginFailed(error.message)); 37 | }) 38 | ); 39 | })); 40 | 41 | @Effect() 42 | logout$: Observable = this.actions$.pipe( 43 | ofType(LoginActionTypes.Logout), 44 | throttleTime(THROTTLE_TIME), 45 | mapTo(new LoginRedirect()) 46 | ); 47 | 48 | @Effect() 49 | loginSuccess$: Observable = this.actions$.pipe( 50 | ofType(LoginActionTypes.LoginSuccess), 51 | mapTo(new DashboardRedirect()) 52 | ); 53 | 54 | @Effect({dispatch: false}) 55 | loginRedirect$: Observable = this.actions$.pipe( 56 | ofType(LoginActionTypes.LogoutRedirect), 57 | tap(action => { 58 | this.router.navigateByUrl('login'); 59 | }) 60 | ); 61 | 62 | @Effect({dispatch: false}) 63 | dashboardRedirect: Observable = this.actions$.pipe( 64 | ofType(LoginActionTypes.DashboardRedirect), 65 | tap(action => { 66 | this.router.navigateByUrl('dashboard'); 67 | }) 68 | ); 69 | 70 | @Effect({dispatch: false}) 71 | authGuardRedirect: Observable = this.actions$.pipe( 72 | ofType(LoginActionTypes.AuthGuardRedirect), 73 | tap(action => { 74 | this.router.navigateByUrl('login'); 75 | }) 76 | ); 77 | 78 | constructor(private authService: AuthService, private router: Router, private actions$: Actions) { 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/app/auth/models/login-credentials.ts: -------------------------------------------------------------------------------- 1 | export interface LoginCredentials { 2 | username: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/auth/models/login-response.ts: -------------------------------------------------------------------------------- 1 | 2 | import {User} from './user'; 3 | 4 | export interface LoginResponse { 5 | jwt: string; 6 | user: User; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/auth/models/user.spec.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user'; 2 | 3 | describe('User', () => { 4 | it('should create an instance', () => { 5 | expect(new User()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/auth/models/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | username?: string; 3 | id?: number; 4 | email?: string; 5 | } 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/auth/reducers/auth.reducer.spec.ts: -------------------------------------------------------------------------------- 1 | import { reducer, initialState } from './auth.reducer'; 2 | 3 | describe('Login Reducer', () => { 4 | describe('an unknown action', () => { 5 | it('should return the previous state', () => { 6 | const action = {} as any; 7 | 8 | const result = reducer(initialState, action); 9 | 10 | expect(result).toBe(initialState); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/app/auth/reducers/auth.reducer.ts: -------------------------------------------------------------------------------- 1 | import {AuthActions, LoginActionTypes} from '../actions/auth.actions'; 2 | import {User} from '../models/user'; 3 | import {createFeatureSelector, createSelector} from '@ngrx/store'; 4 | 5 | export interface State { 6 | authenticated: boolean; 7 | user: User; 8 | jwt: string; 9 | loading: boolean; 10 | errorMessage: string; 11 | } 12 | 13 | // export const selectAuthState = createFeatureSelector('auth'); 14 | // export const selectAuthenticated = (state: State) => state.authenticated; 15 | 16 | export const initialState: State = { 17 | authenticated: false, 18 | user: undefined, 19 | jwt: undefined, 20 | loading: false, 21 | errorMessage: '' 22 | }; 23 | 24 | // TODO: Manage errors 25 | export function reducer(state = initialState, action: AuthActions): State { 26 | 27 | switch (action.type) { 28 | 29 | case LoginActionTypes.Login: 30 | return Object.assign({}, state, { 31 | loading: true 32 | }); 33 | 34 | case LoginActionTypes.LoginSuccess: 35 | return Object.assign({}, state, { 36 | authenticated: true, 37 | loading: false, 38 | errorMessage: '', 39 | user : action.payload.user, 40 | jwt: action.payload.jwt 41 | }); 42 | 43 | case LoginActionTypes.LoginFailed: 44 | return Object.assign({}, state, { 45 | loading: false, 46 | errorMessage: action.payload 47 | }); 48 | 49 | case LoginActionTypes.Logout: 50 | return Object.assign({}, state, { 51 | authenticated: false, 52 | user: undefined 53 | }); 54 | 55 | default: 56 | return state; 57 | } 58 | } 59 | 60 | export const getAuthenticated = (state: State) => state.authenticated; 61 | export const getUser = (state: State) => state.user; 62 | export const getLoading = (state: State) => state.loading; 63 | export const getErrorMessage = (state: State) => state.errorMessage; 64 | export const getJwt = (state: State) => state.jwt; 65 | -------------------------------------------------------------------------------- /src/app/auth/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { createSelector, createFeatureSelector} from '@ngrx/store'; 2 | import * as fromRoot from '../../reducers'; 3 | import * as fromAuth from './auth.reducer'; 4 | 5 | 6 | 7 | export interface State extends fromRoot.State { 8 | auth: fromAuth.State; 9 | } 10 | 11 | export const reducers = { 12 | auth: fromAuth.reducer 13 | }; 14 | 15 | export const selectAuth = createFeatureSelector('auth'); 16 | export const selectAuthUser = createSelector(selectAuth, fromAuth.getUser); 17 | export const selectAuthenticated = createSelector(selectAuth, fromAuth.getAuthenticated); 18 | export const selectJwt = createSelector(selectAuth, fromAuth.getJwt); 19 | export const selectLoading = createSelector(selectAuth, fromAuth.getLoading); 20 | -------------------------------------------------------------------------------- /src/app/auth/services/api-auth.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Observable} from 'rxjs'; 3 | import {AuthService} from './auth.service'; 4 | import {LoginCredentials} from '../models/login-credentials'; 5 | import {LoginResponse} from '../models/login-response'; 6 | import {HttpClient} from '@angular/common/http'; 7 | 8 | export class ApiAuthService extends AuthService { 9 | 10 | constructor(private http: HttpClient) { 11 | super(); 12 | } 13 | 14 | authenticate(credentials: LoginCredentials): Observable { 15 | return this.http.post('http://localhost:1337/auth/local', 16 | {identifier: credentials.username, password: credentials.password} 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/auth/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MockAuthService } from './mock-auth.service'; 4 | 5 | describe('MockAuthService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: MockAuthService = TestBed.get(MockAuthService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/auth/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import {Observable} from 'rxjs'; 2 | import {LoginCredentials} from '../models/login-credentials'; 3 | import {LoginResponse} from '../models/login-response'; 4 | 5 | export abstract class AuthService { 6 | abstract authenticate(credentials: LoginCredentials): Observable; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/auth/services/guards/auth-guard.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthGuardService } from './auth-guard.service'; 4 | 5 | describe('AuthGuardService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AuthGuardService = TestBed.get(AuthGuardService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/auth/services/guards/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import * as fromAuth from '../../reducers'; 3 | import {Store} from '@ngrx/store'; 4 | import {CanActivate} from '@angular/router'; 5 | import {Observable, of} from 'rxjs'; 6 | import {map} from 'rxjs/operators'; 7 | import {AuthGuardRedirect, LoginRedirect} from '../../actions/auth.actions'; 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class AuthGuardService implements CanActivate { 12 | 13 | constructor(private store: Store) { 14 | 15 | } 16 | 17 | canActivate(): Observable { 18 | return this.store.select(fromAuth.selectAuthenticated).pipe( 19 | map(authenticated => { 20 | if (!authenticated) { 21 | this.store.dispatch(new AuthGuardRedirect()); 22 | return false; 23 | } 24 | return true; 25 | }) 26 | ); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/auth/services/interceptors/bearer-token-interceptor.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; 3 | import {Observable} from 'rxjs'; 4 | import * as fromAuth from '../../reducers'; 5 | import {Store} from '@ngrx/store'; 6 | import {first, map, mergeMap} from 'rxjs/operators'; 7 | 8 | @Injectable() 9 | 10 | export class BearerTokenInterceptor implements HttpInterceptor { 11 | 12 | constructor(private store: Store) { 13 | } 14 | 15 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 16 | return this.store.select(fromAuth.selectJwt).pipe( 17 | first(), 18 | mergeMap(jwt => { 19 | // Login should not include Authorization header 20 | if (!req.url.includes('login')) { 21 | return next.handle(req); 22 | } 23 | const authRequest = !!jwt ? req.clone({ 24 | setHeaders: {Authorization: 'Bearer ' + jwt} 25 | }) : req; 26 | return next.handle(authRequest); 27 | }) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/auth/services/interceptors/redirect-unauthorized-interceptor.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; 3 | import {Observable} from 'rxjs'; 4 | import * as fromAuth from '../../reducers'; 5 | import {Store} from '@ngrx/store'; 6 | import {first, map, mergeMap, tap} from 'rxjs/operators'; 7 | import {LoginRedirect} from '../../actions/auth.actions'; 8 | 9 | @Injectable() 10 | 11 | export class RedirectUnauthorizedInterceptor implements HttpInterceptor { 12 | 13 | constructor(private store: Store) { 14 | } 15 | 16 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 17 | return next.handle(req).pipe( 18 | tap((event: HttpEvent) => { 19 | if (event instanceof HttpErrorResponse) { 20 | if (event.status === 401) { 21 | this.store.dispatch(new LoginRedirect()); 22 | } 23 | } 24 | }) 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/auth/services/mock-auth.service.ts: -------------------------------------------------------------------------------- 1 | import {Observable, of} from 'rxjs'; 2 | import {delay} from 'rxjs/operators'; 3 | import {AuthService} from './auth.service'; 4 | import {LoginCredentials} from '../models/login-credentials'; 5 | import {LoginResponse} from '../models/login-response'; 6 | import {HttpClient} from '@angular/common/http'; 7 | 8 | export class MockAuthService extends AuthService { 9 | 10 | 11 | constructor() { 12 | super(); 13 | } 14 | 15 | private MOCKRESPONSE: LoginResponse = { 16 | jwt: 'asdsaa214edsd1ee3eds', 17 | user: {username: 'Ben', email: 'user@email.com'} 18 | }; 19 | 20 | authenticate(credentials: LoginCredentials): Observable { 21 | return of(this.MOCKRESPONSE).pipe(delay(2500)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/auto-generated/auto-generated.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/app/auto-generated/auto-generated.component.css -------------------------------------------------------------------------------- /src/app/auto-generated/auto-generated.component.html: -------------------------------------------------------------------------------- 1 |

2 | auto-generated works! 3 |

4 | -------------------------------------------------------------------------------- /src/app/auto-generated/auto-generated.component.tns.css: -------------------------------------------------------------------------------- 1 | /* Add mobile styles for the component here. */ 2 | -------------------------------------------------------------------------------- /src/app/auto-generated/auto-generated.component.tns.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/auto-generated/auto-generated.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-auto-generated', 5 | templateUrl: './auto-generated.component.html', 6 | styleUrls: ['./auto-generated.component.css'] 7 | }) 8 | export class AutoGeneratedComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | declarations: [], 6 | imports: [ 7 | CommonModule 8 | ] 9 | }) 10 | export class CoreModule { } 11 | -------------------------------------------------------------------------------- /src/app/core/domain/messaging-service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {BehaviorSubject, combineLatest, Observable, of, Subject} from 'rxjs'; 3 | import {Message} from './models/message'; 4 | 5 | @Injectable({providedIn: 'root'}) 6 | export class MessagingService { 7 | private subject = new Subject(); 8 | 9 | post(message: Message) { 10 | this.subject.next(message); 11 | } 12 | 13 | getMessages(): Observable { 14 | return this.subject.asObservable(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/core/domain/models/message.spec.ts: -------------------------------------------------------------------------------- 1 | import { Message } from './message'; 2 | 3 | describe('Message', () => { 4 | it('should create an instance', () => { 5 | expect(new Message()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/core/domain/models/message.ts: -------------------------------------------------------------------------------- 1 | export class Message { 2 | message: string; 3 | } 4 | export type MessageTypes = Message; 5 | -------------------------------------------------------------------------------- /src/app/core/domain/observable-use-case.spec.ts: -------------------------------------------------------------------------------- 1 | import { ObservableUseCase } from './observable-use-case'; 2 | 3 | describe('ObservableUseCase', () => { 4 | it('should create an instance', () => { 5 | expect(new ObservableUseCase()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/core/domain/observable-use-case.ts: -------------------------------------------------------------------------------- 1 | import {Usecase} from './usecase'; 2 | import {Observable} from 'rxjs'; 3 | 4 | export abstract class ObservableUseCase extends Usecase { 5 | 6 | abstract execute(): Observable; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/core/domain/single-usecase.ts: -------------------------------------------------------------------------------- 1 | import {Usecase} from './usecase'; 2 | import {Injectable} from '@angular/core'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class SingleUsecase { 8 | execute(payload: T) { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/core/domain/usecase.ts: -------------------------------------------------------------------------------- 1 | import {Observable, Observer} from 'rxjs'; 2 | import {Injectable} from '@angular/core'; 3 | 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export abstract class Usecase { 9 | abstract execute(); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/core/presentation/concerns/editable.ts: -------------------------------------------------------------------------------- 1 | export interface Editable { 2 | toggleEditing(): void; 3 | startEditing(): void; 4 | stopEditing(): void; 5 | setEditing(editing: boolean); 6 | } 7 | -------------------------------------------------------------------------------- /src/app/core/presentation/presenter.ts: -------------------------------------------------------------------------------- 1 | export class Presenter { 2 | 3 | constructor() { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/core/presentation/viewmodels/view-model.ts: -------------------------------------------------------------------------------- 1 | export abstract class ViewModel { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/app/dashboard/containers/dashboard-page/dashboard-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Dashboard 4 | 5 | Welcome to dashboard : {{ user.username }} 6 | 7 | 8 | 9 | Todolist 10 | 11 | 12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /src/app/dashboard/containers/dashboard-page/dashboard-page.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/app/dashboard/containers/dashboard-page/dashboard-page.component.scss -------------------------------------------------------------------------------- /src/app/dashboard/containers/dashboard-page/dashboard-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DashboardPageComponent } from './dashboard-page.component'; 4 | import { Store, StoreModule } from '@ngrx/store'; 5 | 6 | describe('DashboardPageComponent', () => { 7 | let component: DashboardPageComponent; 8 | let fixture: ComponentFixture; 9 | let store: Store; 10 | 11 | beforeEach(async() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ StoreModule.forRoot({}) ], 14 | declarations: [ DashboardPageComponent ] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(DashboardPageComponent); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/dashboard/containers/dashboard-page/dashboard-page.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; 2 | import {Store} from '@ngrx/store'; 3 | import {Observable} from 'rxjs'; 4 | import * as fromAuth from '../../../auth/reducers'; 5 | import {State} from '../../../auth/reducers'; 6 | import {User} from '../../../auth/models/user'; 7 | 8 | @Component({ 9 | selector: 'app-dashboard-page', 10 | templateUrl: './dashboard-page.component.html', 11 | styleUrls: ['./dashboard-page.component.scss'], 12 | }) 13 | export class DashboardPageComponent implements OnInit { 14 | 15 | user$: Observable; 16 | user: User; 17 | 18 | constructor(private store: Store) { 19 | this.user$ = this.store.select(fromAuth.selectAuthUser); 20 | } 21 | 22 | ngOnInit() { 23 | this.user$.subscribe(user => this.user = user); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/app/dashboard/dashboard-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import {DashboardPageComponent} from './containers/dashboard-page/dashboard-page.component'; 4 | import {AuthGuardService} from '../auth/services/guards/auth-guard.service'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: DashboardPageComponent, 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class DashboardRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { MaterialModule} from '../material/material.module'; 4 | import { DashboardRoutingModule } from './dashboard-routing.module'; 5 | import { DashboardPageComponent } from './containers/dashboard-page/dashboard-page.component'; 6 | import {TodoListComponent} from '../todo/presentation/containers/todo-list/todo-list.component'; 7 | import {TodoModule} from '../todo/todo.module'; 8 | 9 | @NgModule({ 10 | declarations: [DashboardPageComponent], 11 | imports: [ 12 | CommonModule, 13 | MaterialModule, 14 | DashboardRoutingModule, 15 | TodoModule, 16 | ] 17 | }) 18 | export class DashboardModule { } 19 | -------------------------------------------------------------------------------- /src/app/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | 3 | import {DragDropModule} from '@angular/cdk/drag-drop'; 4 | import {ScrollingModule} from '@angular/cdk/scrolling'; 5 | import {CdkTableModule} from '@angular/cdk/table'; 6 | import {CdkTreeModule} from '@angular/cdk/tree'; 7 | import { 8 | MatAutocompleteModule, 9 | MatBadgeModule, 10 | MatBottomSheetModule, 11 | MatButtonModule, 12 | MatButtonToggleModule, 13 | MatCardModule, 14 | MatCheckboxModule, 15 | MatChipsModule, 16 | MatDatepickerModule, 17 | MatDialogModule, 18 | MatDividerModule, 19 | MatExpansionModule, 20 | MatGridListModule, 21 | MatIconModule, 22 | MatInputModule, 23 | MatListModule, 24 | MatMenuModule, 25 | MatNativeDateModule, 26 | MatPaginatorModule, 27 | MatProgressBarModule, 28 | MatProgressSpinnerModule, 29 | MatRadioModule, 30 | MatRippleModule, 31 | MatSelectModule, 32 | MatSidenavModule, 33 | MatSliderModule, 34 | MatSlideToggleModule, 35 | MatSnackBarModule, 36 | MatSortModule, 37 | MatStepperModule, 38 | MatTableModule, 39 | MatTabsModule, 40 | MatToolbarModule, 41 | MatTooltipModule, 42 | MatTreeModule, 43 | } from '@angular/material'; 44 | 45 | @NgModule({ 46 | exports: [ 47 | CdkTableModule, 48 | CdkTreeModule, 49 | DragDropModule, 50 | MatAutocompleteModule, 51 | MatBadgeModule, 52 | MatBottomSheetModule, 53 | MatButtonModule, 54 | MatButtonToggleModule, 55 | MatCardModule, 56 | MatCheckboxModule, 57 | MatChipsModule, 58 | MatStepperModule, 59 | MatDatepickerModule, 60 | MatDialogModule, 61 | MatDividerModule, 62 | MatExpansionModule, 63 | MatGridListModule, 64 | MatIconModule, 65 | MatInputModule, 66 | MatListModule, 67 | MatMenuModule, 68 | MatNativeDateModule, 69 | MatPaginatorModule, 70 | MatProgressBarModule, 71 | MatProgressSpinnerModule, 72 | MatRadioModule, 73 | MatRippleModule, 74 | MatSelectModule, 75 | MatSidenavModule, 76 | MatSliderModule, 77 | MatSlideToggleModule, 78 | MatSnackBarModule, 79 | MatSortModule, 80 | MatTableModule, 81 | MatTabsModule, 82 | MatToolbarModule, 83 | MatTooltipModule, 84 | MatTreeModule, 85 | ScrollingModule, 86 | ] 87 | }) 88 | export class MaterialModule { 89 | } 90 | -------------------------------------------------------------------------------- /src/app/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionReducer, 3 | ActionReducerMap, 4 | MetaReducer 5 | } from '@ngrx/store'; 6 | 7 | import {environment} from '../../environments/environment'; 8 | import {LocalStorageConfig, localStorageSync} from 'ngrx-store-localstorage'; 9 | 10 | export interface State { 11 | 12 | } 13 | 14 | export function sessionStorage(reducer: ActionReducer): ActionReducer { 15 | const config: LocalStorageConfig = { 16 | keys: [ 17 | 'auth' 18 | ], 19 | rehydrate: true, 20 | removeOnUndefined: false 21 | }; 22 | 23 | return localStorageSync(config)(reducer); 24 | } 25 | 26 | export const reducers: ActionReducerMap = {}; 27 | 28 | export const metaReducers: MetaReducer[] = !environment.production ? [sessionStorage] : [sessionStorage]; 29 | -------------------------------------------------------------------------------- /src/app/todo/data/api/api-todo-service.spec.ts: -------------------------------------------------------------------------------- 1 | import { ApiTodoService } from './api-todo-service'; 2 | 3 | describe('ApiTodoService', () => { 4 | it('should create an instance', () => { 5 | expect(new ApiTodoService()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/todo/data/api/api-todo-service.ts: -------------------------------------------------------------------------------- 1 | import {TodoService} from './todo.service'; 2 | import {Todo} from '../../domain/models/todo'; 3 | import {Observable, of} from 'rxjs'; 4 | import {delay} from 'rxjs/operators'; 5 | import {HttpClient} from '@angular/common/http'; 6 | import {Injectable} from '@angular/core'; 7 | import {del} from 'selenium-webdriver/http'; 8 | @Injectable({providedIn: 'root'}) 9 | export class ApiTodoService extends TodoService { 10 | 11 | 12 | static MOCKTODOS: Todo[] = [ 13 | { 14 | id: '0', 15 | text: 'Buy beer (API)', 16 | completed: false, 17 | position: 0, 18 | }, 19 | { 20 | id: '1', 21 | text: 'Buy sausages (API', 22 | completed: true, 23 | position: 1, 24 | }, 25 | { 26 | id: '2', 27 | text: 'Buy life', 28 | completed: true, 29 | position: 1, 30 | } 31 | ]; 32 | 33 | 34 | readonly BASEURL = 'http://192.168.33.10:1337/todos'; 35 | constructor(private http: HttpClient) { 36 | super(); 37 | } 38 | 39 | loadTodos(): Observable { 40 | return this.http.get(this.BASEURL); 41 | // return of(ApiTodoService.MOCKTODOS).pipe(delay(1500)); 42 | } 43 | addTodo(todo: Todo) { 44 | return this.http.post(this.BASEURL, todo); 45 | } 46 | 47 | removeTodo(todo: Todo) { 48 | const url = `${this.BASEURL}/${todo.id}`; 49 | return this.http.delete(url); 50 | } 51 | 52 | updateTodo(id: string, changes: Todo) { 53 | return this.http.put(`${this.BASEURL}/${id}`, changes).pipe( 54 | delay(500) 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/todo/data/api/mock-todo-service.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockTodoService } from './mock-todo-service'; 2 | 3 | describe('MockTodoService', () => { 4 | it('should create an instance', () => { 5 | expect(new MockTodoService()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/todo/data/api/mock-todo-service.ts: -------------------------------------------------------------------------------- 1 | import {TodoService} from './todo.service'; 2 | import {Observable, of} from 'rxjs'; 3 | import {Todo} from '../../domain/models/todo'; 4 | import {delay} from 'rxjs/operators'; 5 | import {Injectable} from '@angular/core'; 6 | 7 | @Injectable({providedIn: 'root'}) 8 | export class MockTodoService extends TodoService { 9 | static MOCKTODOS: Todo[] = [ 10 | { 11 | id: '1', 12 | text: 'Buy beer (mock)', 13 | completed: false, 14 | position: 0, 15 | }, 16 | { 17 | id: '2', 18 | text: 'Buy sausages (mock)', 19 | completed: false, 20 | position: 1, 21 | 22 | }, 23 | { 24 | id: '3', 25 | text: 'Buy a life (mock)', 26 | completed: false, 27 | position: 1, 28 | 29 | } 30 | ]; 31 | 32 | loadTodos(): Observable { 33 | return of(MockTodoService.MOCKTODOS).pipe(delay(1500)); 34 | } 35 | 36 | addTodo(todo: Todo): Observable { 37 | return of({ 38 | id: '3', 39 | text: 'Buy a life (mock)', 40 | completed: false, 41 | position: 1, 42 | 43 | }); 44 | } 45 | 46 | removeTodo(todo: Todo) { 47 | return of({ 48 | id: '3', 49 | text: 'Buy a life (mock)', 50 | completed: false, 51 | position: 1, 52 | 53 | }); 54 | } 55 | 56 | updateTodo(todo: Todo) { 57 | return of({ 58 | id: '3', 59 | text: 'Buy a life (mock)', 60 | completed: false, 61 | position: 1, 62 | 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/todo/data/api/todo.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | 3 | import {TodoService} from './todo.service'; 4 | import {Observable} from 'rxjs'; 5 | import {Todo} from '../../domain/models/todo'; 6 | 7 | describe('TodoService', () => { 8 | beforeEach(() => TestBed.configureTestingModule({})); 9 | 10 | it('should be created', () => { 11 | const service: TodoService = TestBed.get(TodoService); 12 | expect(service).toBeTruthy(); 13 | }); 14 | 15 | it('should load todos', () => { 16 | // Todo: Write test 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/app/todo/data/api/todo.service.ts: -------------------------------------------------------------------------------- 1 | import {Todo} from '../../domain/models/todo'; 2 | import {Observable, of} from 'rxjs'; 3 | 4 | export abstract class TodoService { 5 | abstract loadTodos(): Observable; 6 | abstract addTodo(todo: Todo): Observable; 7 | abstract removeTodo(todo: Todo); 8 | abstract updateTodo(id, changes: Todo); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/todo/domain/models/todo.ts: -------------------------------------------------------------------------------- 1 | export interface Todo { 2 | id?: string; 3 | text?: string; 4 | completed?: boolean; 5 | position?: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/todo/domain/state/actions/todo.actions.ts: -------------------------------------------------------------------------------- 1 | import {Action} from '@ngrx/store'; 2 | import {Todo} from '../../models/todo'; 3 | import {HttpErrorResponse} from '@angular/common/http'; 4 | import {State} from '../reducers/todo.reducer'; 5 | import {Update} from '@ngrx/entity'; 6 | 7 | export enum TodoActionTypes { 8 | LoadTodos = '[Todo] Load Todos', 9 | AddTodo = '[Todo] Add Todo', 10 | AddTodoSuccess = '[Todo] Add Todo Success', 11 | LoadTodoSuccess = '[Todo Api] Load Todos Success', 12 | ApiFailure = '[Todo Api] Load Todos Failure', 13 | RemoveTodo = '[TodoListComponent] Remove Todo', 14 | RemoveTodoSuccess = '[TodoApi] Remove Todo Success', 15 | ToggleTodo = '{TodoListComponent] Toggle Todo', 16 | ToggleTodoSuccess = '[Todo Api] Todo Toggle Success', 17 | UpdateTodoState = '[Storage] Update Todo State', 18 | } 19 | 20 | export class LoadTodos implements Action { 21 | readonly type = TodoActionTypes.LoadTodos; 22 | } 23 | 24 | export class AddTodo implements Action { 25 | readonly type = TodoActionTypes.AddTodo; 26 | 27 | constructor(readonly payload: Todo) { 28 | } 29 | } 30 | 31 | export class AddTodoSuccess implements Action { 32 | readonly type = TodoActionTypes.AddTodoSuccess; 33 | 34 | constructor(readonly payload: Todo) { 35 | } 36 | } 37 | 38 | export class LoadTodoSuccess implements Action { 39 | readonly type = TodoActionTypes.LoadTodoSuccess; 40 | 41 | constructor(readonly payload: Todo[]) { 42 | } 43 | } 44 | 45 | export class ApiFailure implements Action { 46 | readonly type = TodoActionTypes.ApiFailure; 47 | 48 | constructor(readonly payload: HttpErrorResponse) { 49 | } 50 | } 51 | 52 | export class RemoveTodo implements Action { 53 | readonly type = TodoActionTypes.RemoveTodo; 54 | 55 | constructor(readonly payload: Todo) { 56 | } 57 | } 58 | 59 | export class RemoveTodoSuccess implements Action { 60 | readonly type = TodoActionTypes.RemoveTodoSuccess; 61 | 62 | constructor(readonly payload: Todo) { 63 | } 64 | } 65 | 66 | export class ToggleTodo implements Action { 67 | readonly type = TodoActionTypes.ToggleTodo; 68 | constructor(readonly payload: Update) {} 69 | } 70 | 71 | export class ToggleTodoSuccess implements Action { 72 | readonly type = TodoActionTypes.ToggleTodoSuccess; 73 | constructor(readonly payload: Todo) {} 74 | } 75 | 76 | export class UpdateTodoState implements Action { 77 | readonly type = TodoActionTypes.UpdateTodoState; 78 | 79 | constructor(readonly payload: State) { 80 | } 81 | } 82 | 83 | export type TodoActions = 84 | LoadTodos 85 | | LoadTodoSuccess 86 | | AddTodo 87 | | AddTodoSuccess 88 | | ApiFailure 89 | | RemoveTodo 90 | | RemoveTodoSuccess 91 | | UpdateTodoState 92 | | ToggleTodo 93 | | ToggleTodoSuccess; 94 | -------------------------------------------------------------------------------- /src/app/todo/domain/state/effects/todo.effects.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { provideMockActions } from '@ngrx/effects/testing'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { TodoEffects } from './todo.effects'; 6 | 7 | describe('TodoEffects', () => { 8 | let actions$: Observable; 9 | let effects: TodoEffects; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | TodoEffects, 15 | provideMockActions(() => actions$) 16 | ] 17 | }); 18 | 19 | effects = TestBed.get(TodoEffects); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(effects).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/todo/domain/state/effects/todo.effects.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Actions, Effect, ofType} from '@ngrx/effects'; 3 | import { 4 | AddTodoSuccess, 5 | ApiFailure, 6 | LoadTodoSuccess, 7 | RemoveTodo, 8 | RemoveTodoSuccess, 9 | TodoActionTypes, ToggleTodo, UpdateTodoState, ToggleTodoSuccess, LoadTodos 10 | } from '../actions/todo.actions'; 11 | import {catchError, filter, map, mapTo, switchMap, tap} from 'rxjs/operators'; 12 | import {fromEvent, of} from 'rxjs'; 13 | import {TodoService} from '../../../data/api/todo.service'; 14 | import {HttpErrorResponse} from '@angular/common/http'; 15 | import {MessagingService} from '../../../../core/domain/messaging-service'; 16 | import {Todo} from '../../models/todo'; 17 | 18 | @Injectable() 19 | export class TodoEffects { 20 | 21 | @Effect() 22 | loadTodos$ = this.actions$.pipe( 23 | ofType(TodoActionTypes.LoadTodos), 24 | switchMap(action => { 25 | return this.todoService.loadTodos().pipe( 26 | map(todos => { 27 | return new LoadTodoSuccess(todos); 28 | }), 29 | catchError((error: HttpErrorResponse) => { 30 | return of(new ApiFailure(error)); 31 | }) 32 | ); 33 | }) 34 | ); 35 | 36 | @Effect() 37 | addTodo$ = this.actions$.pipe( 38 | ofType(TodoActionTypes.AddTodo), 39 | switchMap(action => { 40 | return this.todoService.addTodo(action.payload).pipe( 41 | map((todo) => { 42 | return new AddTodoSuccess(todo); 43 | } 44 | ), 45 | catchError((e: HttpErrorResponse) => of(new ApiFailure(e))) 46 | ); 47 | }), 48 | ); 49 | 50 | @Effect() 51 | removeTodo$ = this.actions$.pipe( 52 | ofType(TodoActionTypes.RemoveTodo), 53 | switchMap(action => { 54 | return this.todoService.removeTodo(action.payload).pipe( 55 | map((todo) => { 56 | return new RemoveTodoSuccess(action.payload); 57 | } 58 | ), 59 | catchError((e: HttpErrorResponse) => of(new ApiFailure(e))) 60 | ); 61 | }), 62 | ); 63 | 64 | @Effect({dispatch: false}) 65 | apiError$ = this.actions$.pipe( 66 | ofType(TodoActionTypes.ApiFailure), 67 | tap(action => { 68 | this.messagingService.post({message: action.payload.message}); 69 | console.warn(action.payload.message); 70 | }) 71 | ); 72 | /* 73 | @Effect() 74 | onChange$ = fromEvent(window, 'storage').pipe( 75 | filter(evt => evt.key === 'todo'), 76 | filter(evt => evt.newValue !== null), 77 | tap( evt => this.messagingService.post({message: JSON.stringify(evt.newValue)})), 78 | map(evt => { 79 | return new UpdateTodoState(JSON.parse(evt.newValue)); 80 | }) 81 | );*/ 82 | 83 | @Effect() 84 | updateTodo$ = this.actions$.pipe( 85 | ofType(TodoActionTypes.ToggleTodo), 86 | switchMap(action => { 87 | console.warn(action.payload.changes.completed) 88 | return this.todoService.updateTodo(action.payload.id, action.payload.changes).pipe( 89 | map((todo: Todo) => { 90 | return new ToggleTodoSuccess(todo); 91 | } 92 | ), 93 | catchError((e: HttpErrorResponse) => of(new ApiFailure(e))) 94 | ); 95 | }), 96 | ); 97 | 98 | 99 | 100 | constructor(private actions$: Actions, private todoService: TodoService, private messagingService: MessagingService) { 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/app/todo/domain/state/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import {createSelector, createFeatureSelector, MetaReducer, Action, ActionReducer} from '@ngrx/store'; 2 | import * as fromRoot from '../../../../reducers'; 3 | import * as fromTodo from './todo.reducer'; 4 | import {environment} from '../../../../../environments/environment'; 5 | import {sessionStorage} from '../../../../reducers'; 6 | import {TodoActionTypes, UpdateTodoState} from '../actions/todo.actions'; 7 | 8 | 9 | 10 | export interface State extends fromRoot.State { 11 | todo: fromTodo.State; 12 | } 13 | 14 | export const reducers = { 15 | todo: fromTodo.reducer 16 | }; 17 | 18 | 19 | /* 20 | 21 | export function updateStateReducer(reducer: ActionReducer): ActionReducer { 22 | return function(state, action: UpdateTodoState) { 23 | console.warn('metareducer'); 24 | if (action.type === TodoActionTypes.UpdateTodoState) { 25 | return action.payload; 26 | } 27 | return reducer(state, action); 28 | }; 29 | } 30 | 31 | export const metaReducers: MetaReducer[] = [updateStateReducer]; 32 | */ 33 | export const selectTodoState = createFeatureSelector('todo'); 34 | export const selectAllTodos = createSelector(selectTodoState, fromTodo.getTodos); 35 | 36 | -------------------------------------------------------------------------------- /src/app/todo/domain/state/reducers/todo.reducer.spec.ts: -------------------------------------------------------------------------------- 1 | import { reducer, initialState } from './todo.reducer'; 2 | 3 | describe('Todo Reducer', () => { 4 | describe('an unknown action', () => { 5 | it('should return the previous state', () => { 6 | const action = {} as any; 7 | 8 | const result = reducer(initialState, action); 9 | 10 | expect(result).toBe(initialState); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/app/todo/domain/state/reducers/todo.reducer.ts: -------------------------------------------------------------------------------- 1 | import {TodoActions, TodoActionTypes} from '../actions/todo.actions'; 2 | import {Todo} from '../../models/todo'; 3 | import {createEntityAdapter, EntityAdapter, EntityState} from '@ngrx/entity'; 4 | 5 | export const adapter: EntityAdapter = createEntityAdapter(); 6 | 7 | export interface State extends EntityState { 8 | loading: boolean; 9 | loaded: boolean; 10 | } 11 | 12 | export const initialState: State = adapter.getInitialState({ 13 | loading: false, 14 | loaded: true 15 | }); 16 | 17 | export function reducer(state = initialState, action: TodoActions): State { 18 | switch (action.type) { 19 | case TodoActionTypes.LoadTodos: 20 | return Object.assign({}, state, { 21 | loading: true, 22 | loaded: false 23 | }); 24 | case TodoActionTypes.LoadTodoSuccess: 25 | return adapter.addAll(action.payload, state); 26 | 27 | case TodoActionTypes.AddTodoSuccess: 28 | return adapter.addOne(action.payload, state); 29 | 30 | case TodoActionTypes.AddTodo: 31 | return Object.assign({}, state, { 32 | loading: false, 33 | }); 34 | 35 | case TodoActionTypes.RemoveTodoSuccess: 36 | return adapter.removeOne(action.payload.id, state); 37 | 38 | case TodoActionTypes.ToggleTodo: 39 | return adapter.updateOne(action.payload, state); 40 | 41 | case TodoActionTypes.UpdateTodoState: 42 | return action.payload; 43 | 44 | default: 45 | return state; 46 | } 47 | } 48 | 49 | const { 50 | selectIds, 51 | selectEntities, 52 | selectAll, 53 | selectTotal, 54 | } = adapter.getSelectors(); 55 | 56 | export const getTodos = selectAll; 57 | -------------------------------------------------------------------------------- /src/app/todo/domain/usecases/add-todo.ts: -------------------------------------------------------------------------------- 1 | import {Usecase} from '../../../core/domain/usecase'; 2 | import {Todo} from '../models/todo'; 3 | import {Store} from '@ngrx/store'; 4 | import {AddTodo} from '../state/actions/todo.actions'; 5 | import {SingleUsecase} from '../../../core/domain/single-usecase'; 6 | import {Injectable} from '@angular/core'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class AddTodoUsecase extends SingleUsecase { 12 | 13 | 14 | constructor(private store: Store) { 15 | super(); 16 | } 17 | 18 | execute(payload: Todo) { 19 | this.store.dispatch(new AddTodo(payload)); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/todo/domain/usecases/load-todos.ts: -------------------------------------------------------------------------------- 1 | import {Todo} from '../models/todo'; 2 | import {Observable} from 'rxjs'; 3 | import {Store} from '@ngrx/store'; 4 | import {LoadTodos} from '../state/actions/todo.actions'; 5 | import {ObservableUseCase} from '../../../core/domain/observable-use-case'; 6 | import * as fromTodo from '../state/reducers'; 7 | import {Injectable} from '@angular/core'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class LoadTodosUsecase extends ObservableUseCase { 13 | 14 | private constructor(private store: Store) { 15 | super(); 16 | } 17 | 18 | execute(): Observable { 19 | 20 | // Dispatch Ngrx Action in data layer 21 | this.store.dispatch(new LoadTodos()); 22 | 23 | // Forward store data to presentation layer 24 | return this.store.select(fromTodo.selectAllTodos); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/todo/domain/usecases/remove-todo.ts: -------------------------------------------------------------------------------- 1 | import {Usecase} from '../../../core/domain/usecase'; 2 | import {Todo} from '../models/todo'; 3 | import {Store} from '@ngrx/store'; 4 | 5 | import {SingleUsecase} from '../../../core/domain/single-usecase'; 6 | import {Injectable} from '@angular/core'; 7 | import {RemoveTodo} from '../state/actions/todo.actions'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class RemoveTodoUsecase extends SingleUsecase { 13 | 14 | 15 | constructor(private store: Store) { 16 | super(); 17 | } 18 | 19 | execute(payload: Todo) { 20 | this.store.dispatch(new RemoveTodo(payload)); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/todo/domain/usecases/toggle-todo.ts: -------------------------------------------------------------------------------- 1 | import {Usecase} from '../../../core/domain/usecase'; 2 | import {Todo} from '../models/todo'; 3 | import {Store} from '@ngrx/store'; 4 | 5 | import {SingleUsecase} from '../../../core/domain/single-usecase'; 6 | import {Injectable} from '@angular/core'; 7 | import {RemoveTodo, ToggleTodo} from '../state/actions/todo.actions'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class ToggleTodoUsecase extends SingleUsecase { 13 | 14 | 15 | constructor(private store: Store) { 16 | super(); 17 | } 18 | 19 | execute(payload: Todo) { 20 | this.store.dispatch(new ToggleTodo({ 21 | id: payload.id, 22 | changes: { 23 | completed: !payload.completed 24 | } 25 | })); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/todo/presentation/components/todo-item/todo-item.component.html: -------------------------------------------------------------------------------- 1 | 18 |
21 | 22 |
23 | 24 | reorder 25 | 26 |
27 | 28 |
29 | 32 | 33 |
34 | 35 |
36 | 37 | 41 | 42 |
43 | 44 |
46 | {{todo.text}} 47 |
48 |
49 | 55 |
56 | 57 | 58 |
59 | 60 | -------------------------------------------------------------------------------- /src/app/todo/presentation/components/todo-item/todo-item.component.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .todo-completed { 5 | text-decoration: line-through; 6 | } 7 | 8 | .wrapper-test{ 9 | //padding:4px; 10 | 11 | } 12 | 13 | .wrapper-test .remove{ 14 | visibility:hidden; 15 | } 16 | 17 | .wrapper-test:hover .remove{ 18 | visibility:visible; 19 | 20 | } 21 | 22 | .wrapper-test .handle { 23 | visibility:hidden; 24 | 25 | } 26 | .wrapper-test:hover .handle{ 27 | visibility:visible; 28 | } 29 | .wrapper-test:hover{ 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/todo/presentation/components/todo-item/todo-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TodoItemComponent } from './todo-item.component'; 4 | 5 | describe('TodoItemComponent', () => { 6 | let component: TodoItemComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TodoItemComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TodoItemComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/todo/presentation/components/todo-item/todo-item.component.tns.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/todo/presentation/components/todo-item/todo-item.component.tns.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Add your NativeScript specific styles here. 3 | To learn more about styling in NativeScript see: 4 | https://docs.nativescript.org/angular/ui/styling 5 | */ -------------------------------------------------------------------------------- /src/app/todo/presentation/components/todo-item/todo-item.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, Renderer2} from '@angular/core'; 2 | import {Todo} from '../../../domain/models/todo'; 3 | import {TodoViewmodel} from '../../viewmodels/todo-viewmodel'; 4 | import {withIdentifier} from 'codelyzer/util/astQuery'; 5 | import {Editable} from '../../../../core/presentation/concerns/editable'; 6 | import {LoginCredentials} from '../../../../auth/models/login-credentials'; 7 | import {MatCheckboxChange} from '@angular/material'; 8 | 9 | @Component({ 10 | selector: 'app-todo-item', 11 | templateUrl: './todo-item.component.html', 12 | styleUrls: ['./todo-item.component.scss'], 13 | changeDetection: ChangeDetectionStrategy.OnPush 14 | }) 15 | export class TodoItemComponent implements OnInit { 16 | 17 | @Input() todo: TodoViewmodel; 18 | @Output() startEditing = new EventEmitter(); 19 | @Output() stopEditing = new EventEmitter(); 20 | @Output() remove = new EventEmitter(); 21 | @Output() toggle = new EventEmitter(); 22 | 23 | 24 | //todo: add formgroup isdirty check before firing events 25 | constructor(private renderer: Renderer2) { 26 | } 27 | 28 | ngOnInit() { 29 | } 30 | 31 | onInputFocus() { 32 | this.startEditing.emit(); 33 | } 34 | 35 | onInputBlur() { 36 | this.stopEditing.emit(); 37 | } 38 | 39 | onRemove() { 40 | this.remove.emit(); 41 | } 42 | 43 | onCheckboxChanged(event: MatCheckboxChange) { 44 | this.toggle.emit(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/todo/presentation/containers/todo-list/todo-list.component.html: -------------------------------------------------------------------------------- 1 |

Todo

2 | 8 | 9 | 10 | 13 | 14 |
19 |
20 | add 21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 |

Done

32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/app/todo/presentation/containers/todo-list/todo-list.component.scss: -------------------------------------------------------------------------------- 1 | .form { 2 | width: 100% 3 | 4 | } 5 | .form-field { 6 | width: 100% 7 | } 8 | -------------------------------------------------------------------------------- /src/app/todo/presentation/containers/todo-list/todo-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TodoListComponent } from './todo-list.component'; 4 | import { Store, StoreModule } from '@ngrx/store'; 5 | 6 | describe('TodoListComponent', () => { 7 | let component: TodoListComponent; 8 | let fixture: ComponentFixture; 9 | let store: Store; 10 | 11 | beforeEach(async() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ StoreModule.forRoot({}) ], 14 | declarations: [ TodoListComponent ] 15 | }); 16 | 17 | await TestBed.compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(TodoListComponent); 22 | component = fixture.componentInstance; 23 | store = TestBed.get(Store); 24 | 25 | spyOn(store, 'dispatch').and.callThrough(); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/todo/presentation/containers/todo-list/todo-list.component.tns.css: -------------------------------------------------------------------------------- 1 | /* 2 | Add your NativeScript specific styles here. 3 | To learn more about styling in NativeScript see: 4 | https://docs.nativescript.org/angular/ui/styling 5 | */ -------------------------------------------------------------------------------- /src/app/todo/presentation/containers/todo-list/todo-list.component.tns.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/app/todo/presentation/containers/todo-list/todo-list.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; 2 | import {TodoPresenter} from '../../presenter/todo-presenter.service'; 3 | import {TodoViewmodel} from '../../viewmodels/todo-viewmodel'; 4 | import {Observable} from 'rxjs'; 5 | import {filter, map, switchMap} from 'rxjs/operators'; 6 | import {Todo} from '../../../domain/models/todo'; 7 | import {MessagingService} from '../../../../core/domain/messaging-service'; 8 | 9 | @Component({ 10 | selector: 'app-todo-list', 11 | templateUrl: './todo-list.component.html', 12 | styleUrls: ['./todo-list.component.scss'], 13 | }) 14 | export class TodoListComponent implements OnInit { 15 | 16 | constructor(private presenter: TodoPresenter) { 17 | } 18 | 19 | todos$ = this.presenter.loadTodos(); 20 | todos: TodoViewmodel[]; 21 | 22 | ngOnInit() { 23 | this.todos$.subscribe(todos => this.todos = todos); 24 | } 25 | 26 | onTodoClicked(clickedTodo: TodoViewmodel) { 27 | console.warn('onTodoClicked'); 28 | clickedTodo.isEditing = !clickedTodo.isEditing; 29 | } 30 | 31 | onStartEditing(todo: TodoViewmodel) { 32 | console.warn('onStartEditing'); 33 | todo.isEditing = true; 34 | } 35 | 36 | onStopEditing(todo: TodoViewmodel) { 37 | console.warn('onStopEditing'); 38 | todo.isEditing = false; 39 | } 40 | 41 | private getRandomInt(max) { 42 | return Math.floor(Math.random() * Math.floor(max)); 43 | } 44 | 45 | onAddClicked() { 46 | console.warn('onAddclicked'); 47 | this.presenter.addTodo({ 48 | text: 'New Todo' + this.getRandomInt(33), 49 | position: 1, 50 | isEditing: false, 51 | completed: false 52 | }); 53 | } 54 | 55 | onRemove(todo: TodoViewmodel) { 56 | console.warn('onRemove'); 57 | this.presenter.removeTodo(todo); 58 | } 59 | 60 | onToggle(todo: TodoViewmodel) { 61 | return this.presenter.toggleTodo(todo); 62 | } 63 | 64 | get completedTodos() { 65 | return this.todos.filter(todo => todo.completed); 66 | } 67 | 68 | get incompleteTodos() { 69 | return this.todos.filter(todo => !todo.completed); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app/todo/presentation/directives/InputAutofocus.ts: -------------------------------------------------------------------------------- 1 | import { Directive, OnInit, ElementRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appInputAutofocus]' 5 | }) 6 | export class AutofocusDirective implements OnInit { 7 | 8 | constructor(private elementRef: ElementRef) { } 9 | 10 | ngOnInit(): void { 11 | this.elementRef.nativeElement.focus(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/app/todo/presentation/presenter/mappers/mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { Mapper } from './mapper'; 2 | 3 | describe('Mapper', () => { 4 | it('should create an instance', () => { 5 | expect(new Mapper()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/todo/presentation/presenter/mappers/mapper.ts: -------------------------------------------------------------------------------- 1 | export interface Mapper { 2 | mapToModel(viewModel: To): From; 3 | mapToViewmodel(domainModel: From): To; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/todo/presentation/presenter/mappers/todo-mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { TodoMapper } from './todo-mapper'; 2 | 3 | describe('TodoMapper', () => { 4 | it('should create an instance', () => { 5 | expect(new TodoMapper()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/todo/presentation/presenter/mappers/todo-mapper.ts: -------------------------------------------------------------------------------- 1 | import {Mapper} from './mapper'; 2 | import {TodoViewmodel} from '../../viewmodels/todo-viewmodel'; 3 | import {Todo} from '../../../domain/models/todo'; 4 | import {Injectable} from '@angular/core'; 5 | import {TodoModule} from '../../../todo.module'; 6 | import {TodoPresenter} from '../todo-presenter.service'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class TodoMapper implements Mapper { 12 | mapToModel(viewModel: TodoViewmodel): Todo { 13 | return { 14 | text: viewModel.text, 15 | completed: viewModel.completed, 16 | position: viewModel.position, 17 | id: viewModel.id 18 | }; 19 | } 20 | 21 | mapToViewmodel(domainModel: Todo): TodoViewmodel { 22 | const viewmodel: TodoViewmodel = new TodoViewmodel(); 23 | viewmodel.completed = domainModel.completed; 24 | viewmodel.text = domainModel.text; 25 | viewmodel.position = domainModel.position; 26 | viewmodel.id = domainModel.id; 27 | return viewmodel; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/todo/presentation/presenter/todo-presenter.service.ts: -------------------------------------------------------------------------------- 1 | import {Todo} from '../../domain/models/todo'; 2 | import {Injectable} from '@angular/core'; 3 | import {Presenter} from '../../../core/presentation/presenter'; 4 | import {Usecase} from '../../../core/domain/usecase'; 5 | import {Observable, of} from 'rxjs'; 6 | import {LoadTodosUsecase} from '../../domain/usecases/load-todos'; 7 | import {TodoViewmodel} from '../viewmodels/todo-viewmodel'; 8 | import {map, mapTo, mergeMap, switchMap} from 'rxjs/operators'; 9 | import {TodoMapper} from './mappers/todo-mapper'; 10 | import {TodoModule} from '../../todo.module'; 11 | import {TodoListComponent} from '../containers/todo-list/todo-list.component'; 12 | import {AddTodoUsecase} from '../../domain/usecases/add-todo'; 13 | import {RemoveTodoUsecase} from '../../domain/usecases/remove-todo'; 14 | import {ToggleTodoUsecase} from '../../domain/usecases/toggle-todo'; 15 | 16 | @Injectable({ 17 | providedIn: 'root' 18 | }) 19 | export class TodoPresenter extends Presenter { 20 | 21 | constructor( 22 | private loadTodoUsecase: LoadTodosUsecase, 23 | private addTodoUsecase: AddTodoUsecase, 24 | private removeTodoUsecase: RemoveTodoUsecase, 25 | private todoMapper: TodoMapper, 26 | private toggleTodoUsecase: ToggleTodoUsecase) { 27 | super(); 28 | } 29 | 30 | loadTodos(): Observable { 31 | return this.loadTodoUsecase.execute().pipe( 32 | // map model(domain) to viewmodel(presentation) 33 | map((todos) => todos.map(todo => this.todoMapper.mapToViewmodel(todo))) 34 | ); 35 | } 36 | 37 | addTodo(todo: TodoViewmodel) { 38 | this.addTodoUsecase.execute(this.todoMapper.mapToModel(todo)); 39 | } 40 | 41 | removeTodo(todo: TodoViewmodel) { 42 | this.removeTodoUsecase.execute(this.todoMapper.mapToModel(todo)); 43 | } 44 | 45 | updateManyTodos(todos: TodoViewmodel[]) { 46 | 47 | } 48 | 49 | toggleTodo(todo: TodoViewmodel) { 50 | this.toggleTodoUsecase.execute(this.todoMapper.mapToModel(todo)); 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/app/todo/presentation/viewmodels/todo-viewmodel.spec.ts: -------------------------------------------------------------------------------- 1 | import { TodoViewmodel } from './todo-viewmodel'; 2 | 3 | describe('TodoViewmodel', () => { 4 | it('should create an instance', () => { 5 | expect(new TodoViewmodel()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/todo/presentation/viewmodels/todo-viewmodel.ts: -------------------------------------------------------------------------------- 1 | import {ViewModel} from '../../../core/presentation/viewmodels/view-model'; 2 | 3 | export class TodoViewmodel extends ViewModel { 4 | id?: string; 5 | text: string; 6 | completed: boolean; 7 | position: number; 8 | isEditing = false; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/app/todo/todo-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { TodoListComponent} from './presentation/containers/todo-list/todo-list.component'; 4 | 5 | const routes: Routes = [{path: '', component: TodoListComponent}]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class TodoRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/todo/todo.common.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import {TodoListComponent} from './presentation/containers/todo-list/todo-list.component'; 3 | 4 | export const componentDeclarations: any[] = [ 5 | ]; 6 | 7 | export const providerDeclarations: any[] = [ 8 | ]; 9 | 10 | export const routes: Routes = [ 11 | ]; 12 | -------------------------------------------------------------------------------- /src/app/todo/todo.module.tns.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; 2 | import { NativeScriptCommonModule } from 'nativescript-angular/common'; 3 | import {TodoListComponent} from './presentation/containers/todo-list/todo-list.component'; 4 | import {TodoItemComponent} from './presentation/components/todo-item/todo-item.component'; 5 | import {TodoRoutingModule} from './todo-routing.module'; 6 | import {StoreModule} from '@ngrx/store'; 7 | import {EffectsModule} from '@ngrx/effects'; 8 | import {TodoEffects} from './domain/state/effects/todo.effects'; 9 | import {ApiTodoService} from './data/api/api-todo-service'; 10 | import {TodoService} from './data/api/todo.service'; 11 | import * as fromTodo from './domain/state/reducers/todo.reducer'; 12 | import {MockTodoService} from './data/api/mock-todo-service'; 13 | 14 | @NgModule({ 15 | declarations: [TodoListComponent, TodoItemComponent], 16 | imports: [ 17 | NativeScriptCommonModule, 18 | TodoRoutingModule, 19 | StoreModule.forFeature('todo', fromTodo.reducer), 20 | EffectsModule.forFeature([TodoEffects]) 21 | ], 22 | providers: [ 23 | { provide: TodoService, useClass: ApiTodoService} 24 | ], 25 | schemas: [NO_ERRORS_SCHEMA] 26 | }) 27 | export class TodoModule { } 28 | -------------------------------------------------------------------------------- /src/app/todo/todo.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {StoreModule} from '@ngrx/store'; 4 | import * as fromTodo from './domain/state/reducers/todo.reducer'; 5 | import {EffectsModule} from '@ngrx/effects'; 6 | import {TodoEffects} from './domain/state/effects/todo.effects'; 7 | import {TodoListComponent} from './presentation/containers/todo-list/todo-list.component'; 8 | import {TodoItemComponent} from './presentation/components/todo-item/todo-item.component'; 9 | import {TodoRoutingModule} from './todo-routing.module'; 10 | import {TodoService} from './data/api/todo.service'; 11 | import {ApiTodoService} from './data/api/api-todo-service'; 12 | import {AutofocusDirective} from './presentation/directives/InputAutofocus'; 13 | import {MaterialModule} from '../material/material.module'; 14 | import {MockTodoService} from './data/api/mock-todo-service'; 15 | import {FlexLayoutModule} from '@angular/flex-layout'; 16 | import {FormsModule} from '@angular/forms'; 17 | 18 | 19 | @NgModule({ 20 | declarations: [TodoListComponent, TodoItemComponent, AutofocusDirective], 21 | imports: [ 22 | CommonModule, 23 | FormsModule, 24 | TodoRoutingModule, 25 | StoreModule.forFeature('todo', fromTodo.reducer), 26 | EffectsModule.forFeature([TodoEffects]), 27 | MaterialModule, 28 | FlexLayoutModule 29 | ], 30 | providers: [ 31 | {provide: TodoService, useClass: ApiTodoService} 32 | ], 33 | exports: [TodoListComponent] 34 | }) 35 | export class TodoModule { 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` 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/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benarso/angular-cleaner-architecture/b41af9b41b124a0776c2758a560092af547c0a99/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ngrx 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/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-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /src/main.tns.ts: -------------------------------------------------------------------------------- 1 | // this import should be first in order to load some required settings (like globals and reflect-metadata) 2 | import { platformNativeScriptDynamic } from 'nativescript-angular/platform'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | 6 | // A traditional NativeScript application starts by initializing global objects, setting up global CSS rules, creating, and navigating to the main page. 7 | // Angular applications need to take care of their own initialization: modules, components, directives, routes, DI providers. 8 | // A NativeScript Angular app needs to make both paradigms work together, so we provide a wrapper platform object, platformNativeScriptDynamic, 9 | // that sets up a NativeScript application and can bootstrap the Angular framework. 10 | platformNativeScriptDynamic().bootstrapModule(AppModule); 11 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'hammerjs'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ngrx", 3 | "short_name": "Ngrx", 4 | "theme_color": "#1976d2", 5 | "background_color": "#fafafa", 6 | "display": "standalone", 7 | "scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "assets/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "assets/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "assets/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "android": { 3 | "v8Flags": "--expose_gc" 4 | }, 5 | "main": "main.js", 6 | "name": "migration-ng", 7 | "version": "4.1.0" 8 | } 9 | -------------------------------------------------------------------------------- /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 Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | 4 | 5 | html, body { 6 | height: 100% 7 | } 8 | 9 | body { 10 | margin: 0px; 11 | padding: 0px; 12 | } 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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/dist/zone-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: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts", 10 | "**/*.tns.ts", 11 | "**/*.android.ts", 12 | "**/*.ios.ts" 13 | ] 14 | } -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.tns.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "baseUrl": "./", 7 | "paths": { 8 | "~/*": [ 9 | "src/*" 10 | ], 11 | "*": [ 12 | "./node_modules/tns-core-modules/*", 13 | "./node_modules/*" 14 | ] 15 | } 16 | }, 17 | "exclude": [ 18 | "**/*.tns.ts", 19 | "**/*.android.ts", 20 | "**/*.ios.ts", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "use-input-property-decorator": true, 122 | "use-output-property-decorator": true, 123 | "use-host-property-decorator": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-life-cycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "component-class-suffix": true, 129 | "directive-class-suffix": true 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { join, relative, resolve, sep } = require("path"); 2 | 3 | const webpack = require("webpack"); 4 | const nsWebpack = require("nativescript-dev-webpack"); 5 | const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target"); 6 | const { nsReplaceBootstrap } = require("nativescript-dev-webpack/transformers/ns-replace-bootstrap"); 7 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 8 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 9 | const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); 10 | const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin"); 11 | const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); 12 | const { AngularCompilerPlugin } = require("@ngtools/webpack"); 13 | 14 | module.exports = env => { 15 | // Add your custom Activities, Services and other Android app components here. 16 | const appComponents = [ 17 | "tns-core-modules/ui/frame", 18 | "tns-core-modules/ui/frame/activity", 19 | ]; 20 | 21 | const platform = env && (env.android && "android" || env.ios && "ios"); 22 | if (!platform) { 23 | throw new Error("You need to provide a target platform!"); 24 | } 25 | 26 | const projectRoot = __dirname; 27 | 28 | // Default destination inside platforms//... 29 | const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot)); 30 | const appResourcesPlatformDir = platform === "android" ? "Android" : "iOS"; 31 | 32 | const { 33 | // The 'appPath' and 'appResourcesPath' values are fetched from 34 | // the nsconfig.json configuration file 35 | // when bundling with `tns run android|ios --bundle`. 36 | appPath = "app", 37 | appResourcesPath = "app/App_Resources", 38 | 39 | // You can provide the following flags when running 'tns run android|ios' 40 | aot, // --env.aot 41 | snapshot, // --env.snapshot 42 | uglify, // --env.uglify 43 | report, // --env.report 44 | sourceMap, // --env.sourceMap 45 | hmr, // --env.hmr, 46 | } = env; 47 | const externals = (env.externals || []).map((e) => { // --env.externals 48 | return new RegExp(e + ".*"); 49 | }); 50 | 51 | const appFullPath = resolve(projectRoot, appPath); 52 | const appResourcesFullPath = resolve(projectRoot, appResourcesPath); 53 | 54 | const entryModule = `${nsWebpack.getEntryModule(appFullPath)}.ts`; 55 | const entryPath = `.${sep}${entryModule}`; 56 | 57 | const ngCompilerPlugin = new AngularCompilerPlugin({ 58 | hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]), 59 | platformTransformers: aot ? [nsReplaceBootstrap(() => ngCompilerPlugin)] : null, 60 | mainPath: resolve(appPath, entryModule), 61 | tsConfigPath: join(__dirname, "tsconfig.tns.json"), 62 | skipCodeGeneration: !aot, 63 | sourceMap: !!sourceMap, 64 | }); 65 | 66 | const config = { 67 | mode: uglify ? "production" : "development", 68 | context: appFullPath, 69 | externals, 70 | watchOptions: { 71 | ignored: [ 72 | appResourcesFullPath, 73 | // Don't watch hidden files 74 | "**/.*", 75 | ] 76 | }, 77 | target: nativescriptTarget, 78 | entry: { 79 | bundle: entryPath, 80 | }, 81 | output: { 82 | pathinfo: false, 83 | path: dist, 84 | libraryTarget: "commonjs2", 85 | filename: "[name].js", 86 | globalObject: "global", 87 | }, 88 | resolve: { 89 | extensions: [".ts", ".js", ".scss", ".css"], 90 | // Resolve {N} system modules from tns-core-modules 91 | modules: [ 92 | resolve(__dirname, "node_modules/tns-core-modules"), 93 | resolve(__dirname, "node_modules"), 94 | "node_modules/tns-core-modules", 95 | "node_modules", 96 | ], 97 | alias: { 98 | '~': appFullPath 99 | }, 100 | symlinks: true 101 | }, 102 | resolveLoader: { 103 | symlinks: false 104 | }, 105 | node: { 106 | // Disable node shims that conflict with NativeScript 107 | "http": false, 108 | "timers": false, 109 | "setImmediate": false, 110 | "fs": "empty", 111 | "__dirname": false, 112 | }, 113 | devtool: sourceMap ? "inline-source-map" : "none", 114 | optimization: { 115 | splitChunks: { 116 | cacheGroups: { 117 | vendor: { 118 | name: "vendor", 119 | chunks: "all", 120 | test: (module, chunks) => { 121 | const moduleName = module.nameForCondition ? module.nameForCondition() : ''; 122 | return /[\\/]node_modules[\\/]/.test(moduleName) || 123 | appComponents.some(comp => comp === moduleName); 124 | }, 125 | enforce: true, 126 | }, 127 | } 128 | }, 129 | minimize: !!uglify, 130 | minimizer: [ 131 | new UglifyJsPlugin({ 132 | parallel: true, 133 | cache: true, 134 | uglifyOptions: { 135 | output: { 136 | comments: false, 137 | }, 138 | compress: { 139 | // The Android SBG has problems parsing the output 140 | // when these options are enabled 141 | 'collapse_vars': platform !== "android", 142 | sequences: platform !== "android", 143 | } 144 | } 145 | }) 146 | ], 147 | }, 148 | module: { 149 | rules: [ 150 | { 151 | test: new RegExp(entryPath), 152 | use: [ 153 | // Require all Android app components 154 | platform === "android" && { 155 | loader: "nativescript-dev-webpack/android-app-components-loader", 156 | options: { modules: appComponents } 157 | }, 158 | 159 | { 160 | loader: "nativescript-dev-webpack/bundle-config-loader", 161 | options: { 162 | angular: true, 163 | loadCss: !snapshot, // load the application css if in debug mode 164 | } 165 | }, 166 | ].filter(loader => !!loader) 167 | }, 168 | 169 | { test: /\.html$|\.xml$/, use: "raw-loader" }, 170 | 171 | // tns-core-modules reads the app.css and its imports using css-loader 172 | { 173 | test: /[\/|\\]app\.css$/, 174 | use: { 175 | loader: "css-loader", 176 | options: { minimize: false, url: false }, 177 | } 178 | }, 179 | { 180 | test: /[\/|\\]app\.scss$/, 181 | use: [ 182 | { loader: "css-loader", options: { minimize: false, url: false } }, 183 | "sass-loader" 184 | ] 185 | }, 186 | 187 | // Angular components reference css files and their imports using raw-loader 188 | { test: /\.css$/, exclude: /[\/|\\]app\.css$/, use: "raw-loader" }, 189 | { test: /\.scss$/, exclude: /[\/|\\]app\.scss$/, use: ["raw-loader", "resolve-url-loader", "sass-loader"] }, 190 | 191 | { 192 | test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, 193 | use: [ 194 | "nativescript-dev-webpack/moduleid-compat-loader", 195 | "@ngtools/webpack", 196 | ] 197 | }, 198 | 199 | // Mark files inside `@angular/core` as using SystemJS style dynamic imports. 200 | // Removing this will cause deprecation warnings to appear. 201 | { 202 | test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/, 203 | parser: { system: true }, 204 | }, 205 | ], 206 | }, 207 | plugins: [ 208 | // Define useful constants like TNS_WEBPACK 209 | new webpack.DefinePlugin({ 210 | "global.TNS_WEBPACK": "true", 211 | "process": undefined, 212 | }), 213 | // Remove all files from the out dir. 214 | new CleanWebpackPlugin([`${dist}/**/*`]), 215 | // Copy native app resources to out dir. 216 | new CopyWebpackPlugin([ 217 | { 218 | from: `${appResourcesFullPath}/${appResourcesPlatformDir}`, 219 | to: `${dist}/App_Resources/${appResourcesPlatformDir}`, 220 | context: projectRoot 221 | }, 222 | ]), 223 | // Copy assets to out dir. Add your own globs as needed. 224 | new CopyWebpackPlugin([ 225 | { from: { glob: "fonts/**" } }, 226 | { from: { glob: "**/*.jpg" } }, 227 | { from: { glob: "**/*.png" } }, 228 | ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }), 229 | // Generate a bundle starter script and activate it in package.json 230 | new nsWebpack.GenerateBundleStarterPlugin([ 231 | "./vendor", 232 | "./bundle", 233 | ]), 234 | // For instructions on how to set up workers with webpack 235 | // check out https://github.com/nativescript/worker-loader 236 | new NativeScriptWorkerPlugin(), 237 | ngCompilerPlugin, 238 | // Does IPC communication with the {N} CLI to notify events when running in watch mode. 239 | new nsWebpack.WatchStateLoggerPlugin(), 240 | ], 241 | }; 242 | 243 | 244 | if (report) { 245 | // Generate report files for bundles content 246 | config.plugins.push(new BundleAnalyzerPlugin({ 247 | analyzerMode: "static", 248 | openAnalyzer: false, 249 | generateStatsFile: true, 250 | reportFilename: resolve(projectRoot, "report", `report.html`), 251 | statsFilename: resolve(projectRoot, "report", `stats.json`), 252 | })); 253 | } 254 | 255 | if (snapshot) { 256 | config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({ 257 | chunk: "vendor", 258 | angular: true, 259 | requireModules: [ 260 | "reflect-metadata", 261 | "@angular/platform-browser", 262 | "@angular/core", 263 | "@angular/common", 264 | "@angular/router", 265 | "nativescript-angular/platform-static", 266 | "nativescript-angular/router", 267 | ], 268 | projectRoot, 269 | webpackConfig: config, 270 | })); 271 | } 272 | 273 | if (hmr) { 274 | config.plugins.push(new webpack.HotModuleReplacementPlugin()); 275 | } 276 | 277 | return config; 278 | }; 279 | --------------------------------------------------------------------------------