├── .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 |
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 |
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 |
--------------------------------------------------------------------------------