├── .browserslistrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ ├── app.po.ts │ ├── setup.ts │ └── utils │ │ └── index.ts └── tsconfig.e2e.json ├── media ├── .gitkeep ├── README.md └── screenshot.png ├── package-lock.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── core │ │ ├── README.md │ │ ├── constants │ │ │ └── themes.ts │ │ ├── core.module.ts │ │ ├── guard │ │ │ ├── auth.guard.ts │ │ │ ├── module-import.guard.ts │ │ │ └── no-auth.guard.ts │ │ ├── interceptor │ │ │ └── token.interceptor.ts │ │ └── service │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── theme.service.spec.ts │ │ │ └── theme.service.ts │ ├── data │ │ ├── README.md │ │ ├── data.module.ts │ │ ├── schema │ │ │ ├── project.ts │ │ │ └── user.ts │ │ └── service │ │ │ ├── json-api.service.ts │ │ │ ├── json │ │ │ └── data.json │ │ │ ├── project.service.ts │ │ │ └── user.service.ts │ ├── layout │ │ ├── README.md │ │ ├── auth-layout │ │ │ ├── auth-layout.component.html │ │ │ ├── auth-layout.component.scss │ │ │ ├── auth-layout.component.spec.ts │ │ │ └── auth-layout.component.ts │ │ ├── content-layout │ │ │ ├── content-layout.component.html │ │ │ ├── content-layout.component.scss │ │ │ ├── content-layout.component.spec.ts │ │ │ └── content-layout.component.ts │ │ ├── footer │ │ │ ├── footer.component.html │ │ │ ├── footer.component.scss │ │ │ └── footer.component.ts │ │ └── nav │ │ │ ├── nav.component.html │ │ │ ├── nav.component.scss │ │ │ └── nav.component.ts │ ├── modules │ │ ├── README.md │ │ ├── about │ │ │ ├── about-routing.module.ts │ │ │ ├── about.module.ts │ │ │ └── page │ │ │ │ └── about │ │ │ │ ├── about.component.html │ │ │ │ ├── about.component.scss │ │ │ │ ├── about.component.scss-theme.scss │ │ │ │ ├── about.component.spec.ts │ │ │ │ └── about.component.ts │ │ ├── auth │ │ │ ├── auth.module.ts │ │ │ ├── auth.routing.ts │ │ │ └── page │ │ │ │ ├── login │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.scss │ │ │ │ ├── login.component.spec.ts │ │ │ │ └── login.component.ts │ │ │ │ └── register │ │ │ │ ├── register.component.css │ │ │ │ ├── register.component.html │ │ │ │ ├── register.component.spec.ts │ │ │ │ └── register.component.ts │ │ ├── contact │ │ │ ├── contact-routing.module.ts │ │ │ ├── contact.module.ts │ │ │ └── page │ │ │ │ ├── contact.component.scss-theme.scss │ │ │ │ └── contact │ │ │ │ ├── contact.component.html │ │ │ │ ├── contact.component.scss │ │ │ │ ├── contact.component.spec.ts │ │ │ │ └── contact.component.ts │ │ └── home │ │ │ ├── home.module.ts │ │ │ ├── home.routing.ts │ │ │ ├── page │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ ├── home.component.ts │ │ │ ├── project-details │ │ │ │ ├── project-details.component.html │ │ │ │ ├── project-details.component.spec.ts │ │ │ │ └── project-details.component.ts │ │ │ └── project-item │ │ │ │ ├── project-container.component.scss-theme.scss │ │ │ │ ├── project-item.component.html │ │ │ │ ├── project-item.component.scss │ │ │ │ ├── project-item.component.spec.ts │ │ │ │ └── project-item.component.ts │ │ │ └── project-resolver.service.ts │ └── shared │ │ ├── README.md │ │ ├── component │ │ ├── control-messages │ │ │ ├── control-messages.component.html │ │ │ ├── control-messages.component.scss │ │ │ ├── control-messages.component.spec.ts │ │ │ └── control-messages.component.ts │ │ └── spinner │ │ │ ├── spinner.component.html │ │ │ ├── spinner.component.scss │ │ │ ├── spinner.component.spec.ts │ │ │ └── spinner.component.ts │ │ ├── material.module.ts │ │ ├── service │ │ ├── validation.service.spec.ts │ │ └── validation.service.ts │ │ └── shared.module.ts ├── assets │ ├── .gitkeep │ ├── api │ │ ├── auth.json │ │ └── projects.json │ ├── images │ │ ├── bg-screenshot.PNG │ │ ├── mountain.png │ │ ├── winter-photo.jpg │ │ ├── winter-photo2.jpg │ │ └── winter.jpg │ └── scss │ │ ├── _base.scss │ │ └── _colors.scss ├── environments │ ├── .env.ts │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.scss ├── styles │ ├── README.md │ ├── style-reset.scss │ ├── styles-reset.scss-theme.scss │ ├── styles-variables.scss │ └── themes │ │ ├── black-theme.scss │ │ └── light-theme.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json ├── tslint.json └── typings.d.ts └── tsconfig.json /.browserslistrc: -------------------------------------------------------------------------------- 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 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://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 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["projects/**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "parserOptions": { 8 | "project": ["tsconfig.json", "e2e/tsconfig.json"], 9 | "createDefaultProgram": true 10 | }, 11 | "extends": [ 12 | "plugin:@angular-eslint/ng-cli-compat", 13 | "plugin:@angular-eslint/ng-cli-compat--formatting-add-on", 14 | "plugin:@angular-eslint/template/process-inline-templates" 15 | ], 16 | "rules": { 17 | "@typescript-eslint/consistent-type-definitions": "error", 18 | "@typescript-eslint/dot-notation": "off", 19 | "@typescript-eslint/explicit-member-accessibility": [ 20 | "off", 21 | { 22 | "accessibility": "explicit" 23 | } 24 | ], 25 | "id-blacklist": "off", 26 | "id-match": "off", 27 | "no-underscore-dangle": "off", 28 | "prefer-arrow/prefer-arrow-functions": "off", 29 | "no-var": "off" 30 | } 31 | }, 32 | { 33 | "files": ["*.html"], 34 | "extends": ["plugin:@angular-eslint/template/recommended"], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.angular/cache 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | *.html -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "semi": true 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mathis Garberg 4 | Copyright (c) 2019 Tom H Anderson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Angular Folder Structure - Highly Scalable 2 | ======================================== 3 | 4 | ![docs badge](https://readthedocs.org/projects/angular-folder-structure/badge/?version=latest) 5 | 6 | This project is inspired by the article on ITNEXT called 7 | "[How to define a highly scalable folder structure for your Angular project](https://itnext.io/choosing-a-highly-scalable-folder-structure-in-angular-d987de65ec7)" 8 | by Mathis Garberg. Based on best practices from the community, other github 9 | Angular projects, developer experience from production Angular projects, and 10 | contributors to this repository, this project's goal is to *create a skeleton 11 | structure which is flexible for projects big or small*. 12 | 13 | ![login screen screenshot](https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/master/media/screenshot.png) 14 | 15 | Tree Structure 16 | -------------- 17 | 18 | This tree represents the directories this repository proposes be added to a 19 | default Angular application. 20 | 21 | ``` 22 | . 23 | ├── media 24 | └── src 25 | ├── app 26 | │   ├── core 27 | │   ├── data 28 | │   ├── layout 29 | │   ├── module 30 | │   └── shared 31 | └── styles 32 | ``` 33 | 34 | Documentation 35 | ------------- 36 | 37 | [Read The Documentation](https://angular-folder-structure.readthedocs.io/en/latest/#) 38 | for details on each part of the directory structure. 39 | 40 | Demonstration Application 41 | ------------------------- 42 | 43 | [See The Application](https://mathisgarberg.github.io/angular-folder-structure/) 44 | in action. 45 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-folder-structure": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "changeDetection": "OnPush", 14 | "style": "scss" 15 | } 16 | }, 17 | "architect": { 18 | "build": { 19 | "builder": "@angular-devkit/build-angular:browser", 20 | "options": { 21 | "allowedCommonJsDependencies": [ 22 | "rxjs-compat", 23 | "ngx-masonry" 24 | ], 25 | "outputPath": "dist", 26 | "index": "src/index.html", 27 | "main": "src/main.ts", 28 | "polyfills": "src/polyfills.ts", 29 | "tsConfig": "src/tsconfig.app.json", 30 | "assets": [ 31 | { 32 | "glob": "favicon.ico", 33 | "input": "src", 34 | "output": "/" 35 | }, 36 | { 37 | "glob": "**/*", 38 | "input": "src/assets", 39 | "output": "/assets" 40 | } 41 | ], 42 | "styles": [ 43 | { 44 | "input": "src/styles.scss", 45 | "inject": true 46 | } 47 | ], 48 | "stylePreprocessorOptions": { 49 | "includePaths": [ 50 | "./src/styles" 51 | ] 52 | }, 53 | "scripts": [], 54 | "vendorChunk": true, 55 | "extractLicenses": false, 56 | "buildOptimizer": false, 57 | "sourceMap": true, 58 | "optimization": false, 59 | "namedChunks": true 60 | }, 61 | "configurations": { 62 | "production": { 63 | "fileReplacements": [ 64 | { 65 | "replace": "src/environments/environment.ts", 66 | "with": "src/environments/environment.prod.ts" 67 | } 68 | ], 69 | "optimization": true, 70 | "outputHashing": "all", 71 | "sourceMap": false, 72 | "namedChunks": false, 73 | "extractLicenses": true, 74 | "vendorChunk": false, 75 | "buildOptimizer": true, 76 | "budgets": [ 77 | { 78 | "type": "bundle", 79 | "name": "polyfills", 80 | "baseline": "150kb", 81 | "maximumWarning": "50kb", 82 | "maximumError": "100kb" 83 | }, 84 | { 85 | "type": "bundle", 86 | "name": "vendor", 87 | "baseline": "900kb", 88 | "maximumWarning": "100kb", 89 | "maximumError": "200kb" 90 | }, 91 | { 92 | "type": "bundle", 93 | "name": "styles", 94 | "baseline": "280kb", 95 | "maximumWarning": "50kb", 96 | "maximumError": "100kb" 97 | }, 98 | { 99 | "type": "bundle", 100 | "name": "main", 101 | "baseline": "1mb", 102 | "maximumWarning": "1mb", 103 | "maximumError": "1.5mb" 104 | }, 105 | { 106 | "type": "anyComponentStyle", 107 | "maximumWarning": "6kb" 108 | } 109 | ] 110 | } 111 | }, 112 | "defaultConfiguration": "" 113 | }, 114 | "serve": { 115 | "builder": "@angular-devkit/build-angular:dev-server", 116 | "options": { 117 | "browserTarget": "angular-folder-structure:build" 118 | }, 119 | "configurations": { 120 | "production": { 121 | "browserTarget": "angular-folder-structure:build:production" 122 | } 123 | } 124 | }, 125 | "extract-i18n": { 126 | "builder": "@angular-devkit/build-angular:extract-i18n", 127 | "options": { 128 | "browserTarget": "angular-folder-structure:build" 129 | } 130 | }, 131 | "test": { 132 | "builder": "@angular-devkit/build-angular:karma", 133 | "options": { 134 | "main": "src/test.ts", 135 | "polyfills": "src/polyfills.ts", 136 | "tsConfig": "src/tsconfig.spec.json", 137 | "karmaConfig": "src/karma.conf.js", 138 | "styles": [ 139 | { 140 | "input": "src/main.scss", 141 | "inject": true 142 | } 143 | ], 144 | "stylePreprocessorOptions": { 145 | "includePaths": [ 146 | "./src/styles" 147 | ] 148 | }, 149 | "scripts": [], 150 | "assets": [ 151 | { 152 | "glob": "favicon.ico", 153 | "input": "src/", 154 | "output": "/" 155 | }, 156 | { 157 | "glob": "**/*", 158 | "input": "src/assets", 159 | "output": "/assets" 160 | } 161 | ] 162 | }, 163 | "configurations": { 164 | "test": { 165 | "fileReplacements": [ 166 | { 167 | "replace": "src/environments/environment.ts", 168 | "with": "src/environments/environment.test.ts" 169 | } 170 | ] 171 | } 172 | } 173 | }, 174 | "lint": { 175 | "builder": "@angular-eslint/builder:lint", 176 | "options": { 177 | "lintFilePatterns": [ 178 | "src/**/*.ts", 179 | "src/**/*.html" 180 | ] 181 | } 182 | } 183 | } 184 | }, 185 | "angular-folder-structure-e2e": { 186 | "root": "e2e/", 187 | "projectType": "application", 188 | "architect": { 189 | "e2e": { 190 | "builder": "@angular-devkit/build-angular:protractor", 191 | "options": { 192 | "protractorConfig": "e2e/protractor.conf.js", 193 | "devServerTarget": "angular-folder-structure:serve" 194 | }, 195 | "configurations": { 196 | "production": { 197 | "devServerTarget": "angular-folder-structure:serve:production" 198 | } 199 | } 200 | } 201 | } 202 | } 203 | }, 204 | "defaultProject": "angular-folder-structure", 205 | "cli": { 206 | "defaultCollection": "@ngrx/schematics" 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /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: 30000, 8 | specs: [ 9 | './src/setup.ts', 10 | './src/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome', 14 | args: ['--no-sandbox'] 15 | }, 16 | directConnect: true, 17 | baseUrl: 'http://localhost:4200/', 18 | framework: 'jasmine', 19 | jasmineNodeOpts: { 20 | showColors: true, 21 | defaultTimeoutInterval: 60000, 22 | print: function() {} 23 | }, 24 | onPrepare() { 25 | require('ts-node').register({ 26 | project: require('path').join(__dirname, './tsconfig.e2e.json') 27 | }); 28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 29 | } 30 | }; -------------------------------------------------------------------------------- /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.getParagraphText()).toEqual('Welcome to angular-folder-structure!'); 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 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/src/setup.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'protractor'; 2 | 3 | browser.waitForAngularEnabled(false); 4 | -------------------------------------------------------------------------------- /e2e/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'protractor'; 2 | 3 | export const getCurrentRouteUrl = () => 4 | browser.getCurrentUrl().then(url => url.substr(url.lastIndexOf('/') + 1)); 5 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es6", 7 | "types": ["jasmine", "jasminewd2", "node"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /media/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/media/.gitkeep -------------------------------------------------------------------------------- /media/README.md: -------------------------------------------------------------------------------- 1 | Media Directory 2 | =============== 3 | 4 | This directory is part of the structure which `angular-folder-structure` 5 | recommends. For more information about this directory see the 6 | [documentation](https://angular-folder-structure.readthedocs.io/en/latest/media.html). 7 | 8 | -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/media/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-folder-structure", 3 | "version": "2.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "dev": "ng serve --aot", 9 | "build": "ng build", 10 | "test": "npm run lint && ng test --configuration=test", 11 | "lint": "npx ng lint angular-folder-structure", 12 | "e2e": "ng e2e", 13 | "watch": "ng test --configuration=test --browsers ChromeHeadless --watch", 14 | "build:prod": "ng build --configuration production --vendor-chunk", 15 | "clean": "rimraf ./dist/", 16 | "format:write": "prettier {src,e2e,cypress}/**/*.{ts,json,md,scss} --write", 17 | "format:test": "prettier {src,e2e,cypress}/**/*.{ts,json,md,scss} --list-different", 18 | "analyze": "npm run clean && npm run build:prod -- --stats-json && webpack-bundle-analyzer ./dist/stats.json", 19 | "compodoc": "compodoc -p src/tsconfig.app.json" 20 | }, 21 | "husky": { 22 | "hooks": { 23 | "pre-commit": "npm run lint" 24 | } 25 | }, 26 | "commitlint": { 27 | "extends": [ 28 | "@commitlint/config-conventional" 29 | ] 30 | }, 31 | "lint-staged": { 32 | "{src,e2e,cypress}/**/*.{ts,json,md,scss}": [ 33 | "prettier --write", 34 | "git add" 35 | ] 36 | }, 37 | "private": true, 38 | "dependencies": { 39 | "@angular/animations": "^14.1.0", 40 | "@angular/cdk": "^14.1.0", 41 | "@angular/common": "^14.1.0", 42 | "@angular/compiler": "^14.1.0", 43 | "@angular/core": "^14.1.0", 44 | "@angular/forms": "^14.1.0", 45 | "@angular/localize": "^14.1.0", 46 | "@angular/material": "^14.1.0", 47 | "@angular/platform-browser": "^14.1.0", 48 | "@angular/platform-browser-dynamic": "^14.1.0", 49 | "@angular/router": "^14.1.0", 50 | "@fortawesome/angular-fontawesome": "0.10.2", 51 | "@fortawesome/fontawesome-free": "6.1.1", 52 | "@fortawesome/fontawesome-svg-core": "6.1.1", 53 | "@fortawesome/free-brands-svg-icons": "6.1.1", 54 | "@fortawesome/free-solid-svg-icons": "6.1.1", 55 | "@testing-library/angular": "^12.1.0", 56 | "@testing-library/dom": "^5.6.1", 57 | "autoprefixer": "10.4.5", 58 | "bootstrap": "^4.6.0", 59 | "browser-detect": "^0.2.28", 60 | "core-js": "^2.6.12", 61 | "masonry-layout": "^4.2.2", 62 | "ngx-masonry": "13.0.0", 63 | "rxjs": "^7.5.6", 64 | "rxjs-compat": "^6.6.7", 65 | "tslib": "^2.3.1", 66 | "zone.js": "~0.11.4" 67 | }, 68 | "devDependencies": { 69 | "@angular-devkit/build-angular": "~14.1.0", 70 | "@angular-devkit/core": "^14.1.0", 71 | "@angular-eslint/builder": "^14.0.2", 72 | "@angular-eslint/eslint-plugin": "14.0.2", 73 | "@angular-eslint/eslint-plugin-template": "14.0.2", 74 | "@angular-eslint/schematics": "14.0.2", 75 | "@angular-eslint/template-parser": "14.0.2", 76 | "@angular/cli": "^14.1.0", 77 | "@angular/compiler-cli": "^14.1.0", 78 | "@angular/language-service": "^14.1.0", 79 | "@commitlint/cli": "^17.0.3", 80 | "@commitlint/config-conventional": "^7.1.2", 81 | "@compodoc/compodoc": "^1.1.14", 82 | "@types/jasmine": "^3.9.0", 83 | "@types/jasminewd2": "^2.0.10", 84 | "@types/node": "^12.20.24", 85 | "@typescript-eslint/eslint-plugin": "^5.29.0", 86 | "@typescript-eslint/parser": "^5.29.0", 87 | "codelyzer": "^6.0.2", 88 | "eslint": "^8.18.0", 89 | "eslint-plugin-import": "2.26.0", 90 | "eslint-plugin-jsdoc": "39.3.3", 91 | "eslint-plugin-prefer-arrow": "1.2.3", 92 | "husky": "^1.1.3", 93 | "jasmine-core": "4.3.0", 94 | "jasmine-marbles": "^0.9.2", 95 | "jasmine-spec-reporter": "~5.0.0", 96 | "karma": "^6.3.16", 97 | "karma-chrome-launcher": "~3.1.0", 98 | "karma-coverage-istanbul-reporter": "~2.0.4", 99 | "karma-jasmine": "~4.0.0", 100 | "karma-jasmine-html-reporter": "^1.7.0", 101 | "karma-spec-reporter": "~0.0.32", 102 | "lint-staged": "^7.3.0", 103 | "prettier": "^1.19.1", 104 | "protractor": "^7.0.0", 105 | "rimraf": "^2.7.1", 106 | "ts-node": "~5.0.1", 107 | "typescript": "~4.6.4", 108 | "webpack-bundle-analyzer": "^4.5.0" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { AuthLayoutComponent } from './layout/auth-layout/auth-layout.component'; 4 | import { ContentLayoutComponent } from './layout/content-layout/content-layout.component'; 5 | import { NoAuthGuard } from '@core/guard/no-auth.guard'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | redirectTo: '/auth/login', 11 | pathMatch: 'full' 12 | }, 13 | { 14 | path: '', 15 | component: ContentLayoutComponent, 16 | canActivate: [NoAuthGuard], // Should be replaced with actual auth guard 17 | children: [ 18 | { 19 | path: 'dashboard', 20 | loadChildren: () => 21 | import('@modules/home/home.module').then(m => m.HomeModule) 22 | }, 23 | { 24 | path: 'about', 25 | loadChildren: () => 26 | import('@modules/about/about.module').then(m => m.AboutModule) 27 | }, 28 | { 29 | path: 'contact', 30 | loadChildren: () => 31 | import('@modules/contact/contact.module').then(m => m.ContactModule) 32 | } 33 | ] 34 | }, 35 | { 36 | path: 'auth', 37 | component: AuthLayoutComponent, 38 | loadChildren: () => 39 | import('@modules/auth/auth.module').then(m => m.AuthModule) 40 | }, 41 | // Fallback when no prior routes is matched 42 | { path: '**', redirectTo: '/auth/login', pathMatch: 'full' } 43 | ]; 44 | 45 | @NgModule({ 46 | imports: [ 47 | RouterModule.forRoot(routes, { 48 | useHash: true, 49 | relativeLinkResolution: 'legacy' 50 | }) 51 | ], 52 | exports: [RouterModule], 53 | providers: [] 54 | }) 55 | export class AppRoutingModule {} 56 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | describe('AppComponent', () => { 4 | beforeEach( 5 | waitForAsync(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [AppComponent] 8 | }).compileComponents(); 9 | }) 10 | ); 11 | it( 12 | 'should create the app', 13 | waitForAsync(() => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }) 18 | ); 19 | it( 20 | `should have as title 'app'`, 21 | waitForAsync(() => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | const app = fixture.debugElement.componentInstance; 24 | expect(app.title).toEqual('app'); 25 | }) 26 | ); 27 | it( 28 | 'should render title in a h1 tag', 29 | waitForAsync(() => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain( 34 | 'Welcome to angular-folder-structure!' 35 | ); 36 | }) 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent {} 9 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { CoreModule } from '@core/core.module'; 5 | import { SharedModule } from '@shared/shared.module'; 6 | 7 | import { AppComponent } from './app.component'; 8 | import { AppRoutingModule } from './app-routing.module'; 9 | 10 | import { ContentLayoutComponent } from './layout/content-layout/content-layout.component'; 11 | import { NavComponent } from './layout/nav/nav.component'; 12 | import { FooterComponent } from './layout/footer/footer.component'; 13 | 14 | import { AuthModule } from '@modules/auth/auth.module'; 15 | import { AuthLayoutComponent } from './layout/auth-layout/auth-layout.component'; 16 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 17 | 18 | @NgModule({ 19 | declarations: [ 20 | AppComponent, 21 | ContentLayoutComponent, 22 | NavComponent, 23 | FooterComponent, 24 | AuthLayoutComponent 25 | ], 26 | imports: [ 27 | // angular 28 | BrowserModule, 29 | 30 | // 3rd party 31 | AuthModule, 32 | 33 | // core & shared 34 | CoreModule, 35 | SharedModule, 36 | 37 | // app 38 | AppRoutingModule, 39 | 40 | BrowserAnimationsModule 41 | ], 42 | providers: [], 43 | bootstrap: [AppComponent] 44 | }) 45 | export class AppModule {} 46 | -------------------------------------------------------------------------------- /src/app/core/README.md: -------------------------------------------------------------------------------- 1 | Core Module 2 | =========== 3 | 4 | This module is part of the structure which `angular-folder-structure` 5 | recommends. For more information about this module see the 6 | [documentation](https://angular-folder-structure.readthedocs.io/en/latest/core.html). 7 | 8 | -------------------------------------------------------------------------------- /src/app/core/constants/themes.ts: -------------------------------------------------------------------------------- 1 | export const themes = [ 2 | { 3 | name: 'afs-light-theme' 4 | }, 5 | { 6 | name: 'afs-dark-theme' 7 | } 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Optional, SkipSelf } from '@angular/core'; 2 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 3 | 4 | import { AuthGuard } from './guard/auth.guard'; 5 | import { NoAuthGuard } from './guard/no-auth.guard'; 6 | import { throwIfAlreadyLoaded } from './guard/module-import.guard'; 7 | 8 | import { TokenInterceptor } from './interceptor/token.interceptor'; 9 | 10 | @NgModule({ 11 | imports: [HttpClientModule], 12 | providers: [ 13 | AuthGuard, 14 | NoAuthGuard, 15 | { 16 | provide: HTTP_INTERCEPTORS, 17 | useClass: TokenInterceptor, 18 | multi: true 19 | } 20 | ] 21 | }) 22 | export class CoreModule { 23 | constructor(@Optional() @SkipSelf() parentModule: CoreModule) { 24 | throwIfAlreadyLoaded(parentModule, 'CoreModule'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/core/guard/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate } from '@angular/router'; 3 | 4 | @Injectable() 5 | export class AuthGuard implements CanActivate { 6 | canActivate(): boolean { 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/core/guard/module-import.guard.ts: -------------------------------------------------------------------------------- 1 | export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { 2 | if (parentModule) { 3 | throw new Error( 4 | `${moduleName} has already been loaded. Import ${moduleName} modules in the AppModule only.` 5 | ); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/core/guard/no-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate } from '@angular/router'; 3 | 4 | @Injectable() 5 | export class NoAuthGuard implements CanActivate { 6 | canActivate(): boolean { 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/core/interceptor/token.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { Injectable } from '@angular/core'; 3 | import { 4 | HttpRequest, 5 | HttpHandler, 6 | HttpEvent, 7 | HttpInterceptor 8 | } from '@angular/common/http'; 9 | 10 | @Injectable() 11 | export class TokenInterceptor implements HttpInterceptor { 12 | intercept( 13 | request: HttpRequest, 14 | next: HttpHandler 15 | ): Observable> { 16 | return next.handle(request); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/core/service/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthService } from './auth.service'; 4 | 5 | describe('AuthService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AuthService = TestBed.inject(AuthService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/core/service/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { of, Observable, throwError } from 'rxjs'; 3 | 4 | import { User } from '@data/schema/user'; 5 | 6 | interface LoginContextInterface { 7 | username: string; 8 | password: string; 9 | token: string; 10 | } 11 | 12 | const defaultUser = { 13 | username: 'Mathis', 14 | password: '12345', 15 | token: '12345' 16 | }; 17 | 18 | @Injectable({ 19 | providedIn: 'root' 20 | }) 21 | export class AuthService { 22 | token: string; 23 | 24 | login(loginContext: LoginContextInterface): Observable { 25 | const isDefaultUser = 26 | loginContext.username === defaultUser.username && 27 | loginContext.password === defaultUser.password; 28 | 29 | if (isDefaultUser) { 30 | return of(defaultUser); 31 | } 32 | 33 | return throwError('Invalid username or password'); 34 | } 35 | 36 | logout(): Observable { 37 | return of(false); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/core/service/theme.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ThemeService } from './theme.service'; 4 | 5 | describe('ThemeService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: ThemeService = TestBed.inject(ThemeService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/core/service/theme.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, BehaviorSubject } from 'rxjs'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class ThemeService { 8 | private isDarkTheme: BehaviorSubject; 9 | 10 | constructor() { 11 | this.isDarkTheme = new BehaviorSubject( 12 | localStorage.getItem('isDarkTheme') === 'true' 13 | ); 14 | } 15 | 16 | setDarkTheme(isDarkTheme: boolean) { 17 | this.isDarkTheme.next(isDarkTheme); 18 | localStorage.setItem('isDarkTheme', this.isDarkTheme.value.toString()); 19 | } 20 | 21 | getDarkTheme(): Observable { 22 | return this.isDarkTheme; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/data/README.md: -------------------------------------------------------------------------------- 1 | Data Module 2 | =========== 3 | 4 | This module is part of the structure which `angular-folder-structure` 5 | recommends. For more information about this module see the 6 | [documentation](https://angular-folder-structure.readthedocs.io/en/latest/data.html). 7 | 8 | -------------------------------------------------------------------------------- /src/app/data/data.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 DataModule { } 11 | -------------------------------------------------------------------------------- /src/app/data/schema/project.ts: -------------------------------------------------------------------------------- 1 | export interface Project { 2 | id: number; 3 | link: string; 4 | title: string; 5 | thumbnail: string; 6 | description: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/data/schema/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | username: string; 3 | password: string; 4 | token: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/data/service/json-api.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, of } from 'rxjs'; 3 | import data from './json/data.json'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class JsonApiService { 9 | get(url: string): Observable { 10 | switch (url) { 11 | case '/projects': 12 | return of(data.projects); 13 | default: 14 | const id = url.substring(url.lastIndexOf('/') + 1); 15 | return of(data.projects[id]); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/data/service/json/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ 3 | { 4 | "id": 1, 5 | "thumbnail": "https://images.unsplash.com/photo-1520769669658-f07657f5a307?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80", 6 | "title": "Visit Norway", 7 | "description": "Kongeriket Norge er et nordisk, europeisk land og en selvstendig stat vest på Den skandinaviske halvøy. Geografisk sett er landet langt og smalt. På den langstrakte kysten mot Nord-Atlanteren befinner Norges vidkjente fjorder seg. Kongeriket Norge omfatter, i tillegg til fastlandet, Jan Mayen og Svalbard" 8 | }, 9 | { 10 | "id": 2, 11 | "thumbnail": "https://images.unsplash.com/photo-1521109762031-b71a005c25b7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1351&q=80", 12 | "title": "Visit Norway", 13 | "description": "Kongeriket Norge er et nordisk, europeisk land og en selvstendig stat vest på Den skandinaviske halvøy. Geografisk sett er landet langt og smalt. På den langstrakte kysten mot Nord-Atlanteren befinner Norges vidkjente fjorder seg. Kongeriket Norge omfatter, i tillegg til fastlandet, Jan Mayen og Svalbard" 14 | }, 15 | { 16 | "id": 3, 17 | "thumbnail": "https://images.unsplash.com/photo-1531504060587-e6811129c0f2?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1351&q=80", 18 | "title": "Visit Norway", 19 | "description": "Kongeriket Norge er et nordisk, europeisk land og en selvstendig stat vest på Den skandinaviske halvøy. Geografisk sett er landet langt og smalt. På den langstrakte kysten mot Nord-Atlanteren befinner Norges vidkjente fjorder seg. Kongeriket Norge omfatter, i tillegg til fastlandet, Jan Mayen og Svalbard" 20 | }, 21 | { 22 | "id": 4, 23 | "thumbnail": "https://images.unsplash.com/photo-1475066392170-59d55d96fe51?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2134&q=80", 24 | "title": "Visit Norway", 25 | "description": "Kongeriket Norge er et nordisk, europeisk land og en selvstendig stat vest på Den skandinaviske halvøy. Geografisk sett er landet langt og smalt. På den langstrakte kysten mot Nord-Atlanteren befinner Norges vidkjente fjorder seg. Kongeriket Norge omfatter, i tillegg til fastlandet, Jan Mayen og Svalbard" 26 | }, 27 | { 28 | "id": 5, 29 | "thumbnail": "https://images.unsplash.com/photo-1518124880777-cf8c82231ffb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1404&q=80", 30 | "title": "Visit Norway", 31 | "description": "Kongeriket Norge er et nordisk, europeisk land og en selvstendig stat vest på Den skandinaviske halvøy. Geografisk sett er landet langt og smalt. På den langstrakte kysten mot Nord-Atlanteren befinner Norges vidkjente fjorder seg. Kongeriket Norge omfatter, i tillegg til fastlandet, Jan Mayen og Svalbard" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /src/app/data/service/project.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { Project } from '../schema/project'; 5 | import { JsonApiService } from './json-api.service'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class ProjectService { 11 | constructor(private jsonApiService: JsonApiService) {} 12 | 13 | getAll(): Observable> { 14 | return this.jsonApiService.get('/projects'); 15 | } 16 | 17 | getSingle(id: number): Observable { 18 | return this.jsonApiService.get(`/projects/${id}`); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/data/service/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { JsonApiService } from './json-api.service'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class UserService { 8 | constructor( 9 | private jsonApiService: JsonApiService 10 | ) {} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/layout/README.md: -------------------------------------------------------------------------------- 1 | Layout Directory 2 | ================ 3 | 4 | This directory is part of the structure which `angular-folder-structure` 5 | recommends. For more information about this directory see the 6 | [documentation](https://angular-folder-structure.readthedocs.io/en/latest/layout.html). 7 | 8 | -------------------------------------------------------------------------------- /src/app/layout/auth-layout/auth-layout.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |

8 | 9 |

10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /src/app/layout/auth-layout/auth-layout.component.scss: -------------------------------------------------------------------------------- 1 | .full-width-image { 2 | min-height: 100vh; 3 | width: 100vw; 4 | background: url('../../../assets/images/winter.jpg') no-repeat center center 5 | fixed; 6 | background-size: cover; 7 | } 8 | 9 | .auth-container { 10 | position: absolute; 11 | top: 0; 12 | bottom: 0; 13 | left: 0; 14 | right: 0; 15 | } 16 | 17 | .auth-box { 18 | display: flex; 19 | flex-direction: column; 20 | justify-content: center; 21 | align-items: center; 22 | width: 100%; 23 | min-height: 100%; 24 | } 25 | 26 | .card { 27 | background-color: rgba(42, 42, 42, 0.8); 28 | color: #fff; 29 | } 30 | -------------------------------------------------------------------------------- /src/app/layout/auth-layout/auth-layout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AuthLayoutComponent } from './auth-layout.component'; 4 | 5 | describe('AuthLayoutComponent', () => { 6 | let component: AuthLayoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [AuthLayoutComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(AuthLayoutComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/layout/auth-layout/auth-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-auth-layout', 5 | templateUrl: './auth-layout.component.html', 6 | styleUrls: ['./auth-layout.component.scss'] 7 | }) 8 | export class AuthLayoutComponent { 9 | } 10 | -------------------------------------------------------------------------------- /src/app/layout/content-layout/content-layout.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |
8 | 9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /src/app/layout/content-layout/content-layout.component.scss: -------------------------------------------------------------------------------- 1 | .mat-app-background { 2 | height: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/layout/content-layout/content-layout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { ContentLayoutComponent } from './content-layout.component'; 4 | 5 | describe('ContentLayoutComponent', () => { 6 | let component: ContentLayoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ContentLayoutComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(ContentLayoutComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/layout/content-layout/content-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { OverlayContainer } from '@angular/cdk/overlay'; 3 | import { map } from 'rxjs/operators'; 4 | 5 | import { themes } from '@core/constants/themes'; 6 | import { ThemeService } from '@core/service/theme.service'; 7 | 8 | @Component({ 9 | selector: 'app-content-layout', 10 | templateUrl: './content-layout.component.html', 11 | styleUrls: ['./content-layout.component.scss'] 12 | }) 13 | export class ContentLayoutComponent implements OnInit { 14 | currentTheme: string; 15 | 16 | currentActiveTheme$ = this.themeService.getDarkTheme().pipe( 17 | map((isDarkTheme: boolean) => { 18 | const [lightTheme, darkTheme] = themes; 19 | 20 | this.currentTheme = isDarkTheme ? lightTheme.name : darkTheme.name; 21 | 22 | if (this.overlayContainer) { 23 | const overlayContainerClasses = this.overlayContainer.getContainerElement() 24 | .classList; 25 | const themeClassesToRemove = Array.from( 26 | overlayContainerClasses 27 | ).filter((item: string) => item.includes('-theme')); 28 | if (themeClassesToRemove.length) { 29 | overlayContainerClasses.remove(...themeClassesToRemove); 30 | } 31 | overlayContainerClasses.add(this.currentTheme); 32 | } 33 | 34 | return this.currentTheme; 35 | }) 36 | ); 37 | 38 | private overlayContainer: OverlayContainer; 39 | 40 | constructor(private themeService: ThemeService) {} 41 | 42 | ngOnInit(): void { 43 | if (this.overlayContainer) { 44 | this.overlayContainer 45 | .getContainerElement() 46 | .classList.add(this.currentTheme); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/layout/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /src/app/layout/footer/footer.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/layout/footer/footer.component.scss -------------------------------------------------------------------------------- /src/app/layout/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | templateUrl: './footer.component.html', 6 | styleUrls: ['./footer.component.scss'] 7 | }) 8 | export class FooterComponent { 9 | currentYear = new Date().getUTCFullYear(); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/layout/nav/nav.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 | Angular Folder Structure 7 | 8 | 9 | Angular Folder Structure 10 | 11 | 12 | 16 | 17 | Change Theme 18 | 19 | 20 | 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /src/app/layout/nav/nav.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/layout/nav/nav.component.scss -------------------------------------------------------------------------------- /src/app/layout/nav/nav.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { ThemeService } from '@core/service/theme.service'; 5 | import { environment } from '@env'; 6 | 7 | @Component({ 8 | selector: 'app-nav', 9 | templateUrl: './nav.component.html', 10 | styleUrls: ['./nav.component.scss'] 11 | }) 12 | export class NavComponent implements OnInit { 13 | public version = environment.version; 14 | public repoUrl = 'https://github.com/mathisGarberg/angular-folder-structure'; 15 | 16 | public isDarkTheme$: Observable; 17 | 18 | navItems = [ 19 | { link: '/dashboard/home', title: 'Home' }, 20 | { link: '/about', title: 'About' }, 21 | { link: '/contact', title: 'Contact' } 22 | ]; 23 | 24 | constructor(private themeService: ThemeService) {} 25 | 26 | ngOnInit() { 27 | this.isDarkTheme$ = this.themeService.getDarkTheme(); 28 | } 29 | 30 | toggleTheme(checked: boolean) { 31 | this.themeService.setDarkTheme(checked); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/modules/README.md: -------------------------------------------------------------------------------- 1 | Module Directory 2 | ================ 3 | 4 | This directory is part of the structure which `angular-folder-structure` 5 | recommends. For more information about this directory see the 6 | [documentation](https://angular-folder-structure.readthedocs.io/en/latest/module.html). 7 | 8 | -------------------------------------------------------------------------------- /src/app/modules/about/about-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { AboutComponent } from './page/about/about.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: AboutComponent 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class AboutRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/modules/about/about.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { SharedModule } from '@shared/shared.module'; 4 | 5 | import { AboutRoutingModule } from './about-routing.module'; 6 | import { AboutComponent } from './page/about/about.component'; 7 | 8 | @NgModule({ 9 | declarations: [AboutComponent], 10 | imports: [AboutRoutingModule, SharedModule] 11 | }) 12 | export class AboutModule {} 13 | -------------------------------------------------------------------------------- /src/app/modules/about/page/about/about.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Angular Folder Structure

3 | 4 |

5 | Based on best practices from the community, other github Angular projects, 6 | developer experience from production Angular projects, and contributors, 7 | this project goal is to create a skeleton structure which is flexible 8 | for projects big or small. 9 |

10 | 11 |

12 | This project defines directory structure in an Angular application and it 13 | is a working application. The code for this project can be found at 14 | 15 | https://github.com/mathisGarberg/angular-folder-structure 16 | 17 |

18 |
-------------------------------------------------------------------------------- /src/app/modules/about/page/about/about.component.scss: -------------------------------------------------------------------------------- 1 | #content { 2 | margin-top: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/modules/about/page/about/about.component.scss-theme.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/modules/about/page/about/about.component.scss-theme.scss -------------------------------------------------------------------------------- /src/app/modules/about/page/about/about.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AboutComponent } from './about.component'; 4 | 5 | describe('AboutComponent', () => { 6 | let component: AboutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [AboutComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(AboutComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/modules/about/page/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-about', 5 | templateUrl: './about.component.html', 6 | styleUrls: ['./about.component.scss'] 7 | }) 8 | export class AboutComponent {} 9 | -------------------------------------------------------------------------------- /src/app/modules/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { LoginComponent } from './page/login/login.component'; 4 | import { RegisterComponent } from './page/register/register.component'; 5 | 6 | import { SharedModule } from '@shared/shared.module'; 7 | import { AuthRoutingModule } from './auth.routing'; 8 | 9 | @NgModule({ 10 | declarations: [LoginComponent, RegisterComponent], 11 | imports: [AuthRoutingModule, SharedModule] 12 | }) 13 | export class AuthModule {} 14 | -------------------------------------------------------------------------------- /src/app/modules/auth/auth.routing.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { LoginComponent } from './page/login/login.component'; 5 | import { RegisterComponent } from './page/register/register.component'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | redirectTo: '/auth/login', 11 | pathMatch: 'full' 12 | }, 13 | { 14 | path: '', 15 | children: [ 16 | { 17 | path: 'login', 18 | component: LoginComponent 19 | }, 20 | { 21 | path: 'register', 22 | component: RegisterComponent 23 | } 24 | ] 25 | } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [RouterModule.forChild(routes)], 30 | exports: [RouterModule] 31 | }) 32 | export class AuthRoutingModule { } 33 | -------------------------------------------------------------------------------- /src/app/modules/auth/page/login/login.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | {{ error }} 8 |
9 | 10 |
11 |
12 |
13 | 17 |
18 |
19 | 23 |
24 |
25 | 31 |
32 | Don't have an account? Sign up here! 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/app/modules/auth/page/login/login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/modules/auth/page/login/login.component.scss -------------------------------------------------------------------------------- /src/app/modules/auth/page/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } 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( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [LoginComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(LoginComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/modules/auth/page/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { 4 | UntypedFormGroup, 5 | UntypedFormBuilder, 6 | UntypedFormControl 7 | } from '@angular/forms'; 8 | import { tap, delay, finalize, catchError } from 'rxjs/operators'; 9 | import { of, Subscription } from 'rxjs'; 10 | 11 | import { AuthService } from '@core/service/auth.service'; 12 | 13 | @Component({ 14 | selector: 'app-login', 15 | templateUrl: './login.component.html', 16 | styleUrls: ['./login.component.scss'] 17 | }) 18 | export class LoginComponent implements OnDestroy { 19 | error: string; 20 | isLoading: boolean; 21 | loginForm: UntypedFormGroup; 22 | 23 | private sub = new Subscription(); 24 | 25 | constructor( 26 | private formBuilder: UntypedFormBuilder, 27 | private router: Router, 28 | private authService: AuthService 29 | ) { 30 | this.buildForm(); 31 | } 32 | 33 | get f() { 34 | return this.loginForm.controls; 35 | } 36 | 37 | login() { 38 | this.isLoading = true; 39 | 40 | const credentials = this.loginForm.value; 41 | 42 | this.sub = this.authService 43 | .login(credentials) 44 | .pipe( 45 | delay(1500), 46 | tap(() => this.router.navigate(['/dashboard/home'])), 47 | finalize(() => (this.isLoading = false)), 48 | catchError(error => of((this.error = error))) 49 | ) 50 | .subscribe(); 51 | } 52 | 53 | ngOnDestroy(): void { 54 | this.sub.unsubscribe(); 55 | } 56 | 57 | private buildForm(): void { 58 | this.loginForm = new UntypedFormGroup({ 59 | username: new UntypedFormControl(''), 60 | password: new UntypedFormControl('') 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/app/modules/auth/page/register/register.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/modules/auth/page/register/register.component.css -------------------------------------------------------------------------------- /src/app/modules/auth/page/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |
7 |
8 | 11 |
12 |
13 | 16 |
17 |
18 | 21 |
22 |
23 | 26 |
27 | Got an account already? Log in here! 28 |
29 | -------------------------------------------------------------------------------- /src/app/modules/auth/page/register/register.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { RegisterComponent } from './register.component'; 4 | 5 | describe('RegisterComponent', () => { 6 | let component: RegisterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [RegisterComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(RegisterComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/modules/auth/page/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-register', 5 | templateUrl: './register.component.html', 6 | styleUrls: ['./register.component.css'] 7 | }) 8 | export class RegisterComponent { 9 | } 10 | -------------------------------------------------------------------------------- /src/app/modules/contact/contact-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | import { ContactComponent } from './page/contact/contact.component'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: '', 7 | component: ContactComponent 8 | } 9 | ]; 10 | 11 | export const contactRoutes = RouterModule.forChild(routes); 12 | -------------------------------------------------------------------------------- /src/app/modules/contact/contact.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { SharedModule } from '@shared/shared.module'; 4 | import { contactRoutes } from './contact-routing.module'; 5 | import { ContactComponent } from './page/contact/contact.component'; 6 | 7 | @NgModule({ 8 | declarations: [ContactComponent], 9 | imports: [contactRoutes, SharedModule] 10 | }) 11 | export class ContactModule {} 12 | -------------------------------------------------------------------------------- /src/app/modules/contact/page/contact.component.scss-theme.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/modules/contact/page/contact.component.scss-theme.scss -------------------------------------------------------------------------------- /src/app/modules/contact/page/contact/contact.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Contact Angular Folder Structure 4 |

5 | 6 |

7 | The best way to contact this project is by 8 | 9 | opening an issue 10 | on the repository. 11 |

12 | 13 |
14 | -------------------------------------------------------------------------------- /src/app/modules/contact/page/contact/contact.component.scss: -------------------------------------------------------------------------------- 1 | #content { 2 | margin-top: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/modules/contact/page/contact/contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { ContactComponent } from './contact.component'; 7 | 8 | describe('ContactComponent', () => { 9 | let component: ContactComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach( 13 | waitForAsync(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [ContactComponent] 16 | }).compileComponents(); 17 | }) 18 | ); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(ContactComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/modules/contact/page/contact/contact.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-contact', 5 | templateUrl: './contact.component.html', 6 | styleUrls: ['./contact.component.scss'] 7 | }) 8 | export class ContactComponent {} 9 | -------------------------------------------------------------------------------- /src/app/modules/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { NgxMasonryModule } from 'ngx-masonry'; 3 | import { SharedModule } from '@shared/shared.module'; 4 | 5 | import { HomeComponent } from './page/home.component'; 6 | import { ProjectItemComponent } from './page/project-item/project-item.component'; 7 | import { ProjectDetailsComponent } from './page/project-details/project-details.component'; 8 | 9 | import { HomeRoutingModule } from './home.routing'; 10 | 11 | @NgModule({ 12 | declarations: [HomeComponent, ProjectItemComponent, ProjectDetailsComponent], 13 | imports: [SharedModule, NgxMasonryModule, HomeRoutingModule], 14 | exports: [], 15 | providers: [] 16 | }) 17 | export class HomeModule {} 18 | -------------------------------------------------------------------------------- /src/app/modules/home/home.routing.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { ProjectResolver } from './project-resolver.service'; 4 | import { HomeComponent } from './page/home.component'; 5 | import { ProjectDetailsComponent } from './page/project-details/project-details.component'; 6 | 7 | export const routes: Routes = [ 8 | { 9 | path: '', 10 | redirectTo: 'home', 11 | pathMatch: 'full' 12 | }, 13 | { 14 | path: 'home', 15 | component: HomeComponent 16 | }, 17 | { 18 | path: 'projects/:id', 19 | component: ProjectDetailsComponent, 20 | resolve: { 21 | project: ProjectResolver 22 | } 23 | } 24 | ]; 25 | 26 | @NgModule({ 27 | imports: [RouterModule.forChild(routes)], 28 | exports: [RouterModule] 29 | }) 30 | export class HomeRoutingModule {} 31 | -------------------------------------------------------------------------------- /src/app/modules/home/page/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Projects

4 |
5 | 6 | 7 |
8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /src/app/modules/home/page/home.component.scss: -------------------------------------------------------------------------------- 1 | .masonry-item { 2 | width: 350px; 3 | margin-bottom: 1rem; 4 | margin-right: 1rem; 5 | } 6 | 7 | #content { 8 | margin-top: 1rem; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/modules/home/page/home.component.spec.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/modules/home/page/home.component.spec.ts -------------------------------------------------------------------------------- /src/app/modules/home/page/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { ProjectService } from '@data/service/project.service'; 5 | import { Project } from '@data/schema/project'; 6 | 7 | @Component({ 8 | selector: 'app-home', 9 | templateUrl: './home.component.html', 10 | styleUrls: ['./home.component.scss'] 11 | }) 12 | export class HomeComponent { 13 | projects$: Observable = this.projectService.getAll(); 14 | 15 | constructor(private projectService: ProjectService) {} 16 | } 17 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-details/project-details.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | {{ (project$ | async)?.title }} 6 | Dog Breed 7 |
8 | Photo of a Shiba Inu 9 | 10 |

11 | The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. 12 | A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally 13 | bred for hunting. 14 |

15 |
16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-details/project-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { ProjectDetailsComponent } from './project-details.component'; 4 | 5 | describe('ProjectDetailsComponent', () => { 6 | let component: ProjectDetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ProjectDetailsComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(ProjectDetailsComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-details/project-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | import { Project } from '@data/schema/project'; 6 | 7 | @Component({ 8 | selector: 'app-project-details', 9 | templateUrl: './project-details.component.html' 10 | }) 11 | export class ProjectDetailsComponent implements OnInit { 12 | project$: Observable; 13 | 14 | constructor(private route: ActivatedRoute) {} 15 | 16 | ngOnInit(): void { 17 | this.project$ = this.route.data.pipe(map(data => data.project)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-item/project-container.component.scss-theme.scss: -------------------------------------------------------------------------------- 1 | @use '@angular/material' as mat; 2 | @import '@angular/material/theming'; 3 | 4 | @mixin my-project-container-component-theme($theme) { 5 | $accent: map-get($theme, accent); 6 | 7 | .active { 8 | color: mat.get-color-from-palette($accent, default-contrast); 9 | background-color: mat.get-color-from-palette($accent); 10 | 11 | &:hover { 12 | color: mat.get-color-from-palette($accent, default-contrast); 13 | background-color: mat.get-color-from-palette($accent); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-item/project-item.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | {{ project.title }} 7 | Dog Breed 8 |
9 | Photo of a Shiba Inu 10 | 11 |

12 | The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. 13 | A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally 14 | bred for hunting. 15 |

16 |
17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 |
25 | {{ project.title }} 26 | Dog Breed 27 |
28 | 29 |

30 | The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. 31 | A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally 32 | bred for hunting. 33 |

34 |
35 | 36 | 37 | 38 |
39 |
40 |
41 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-item/project-item.component.scss: -------------------------------------------------------------------------------- 1 | .card-container { 2 | perspective: 800px; 3 | } 4 | 5 | .flippable-card { 6 | transform-style: preserve-3d; 7 | transition: transform 1s; 8 | } 9 | 10 | .flippable-card mat-card { 11 | margin: 0; 12 | display: block; 13 | backface-visibility: hidden; 14 | } 15 | 16 | .flippable-card .back { 17 | background: #23262d; 18 | color: #fff; 19 | transform: rotateY(180deg); 20 | } 21 | 22 | .flippable-card.flipped { 23 | transform: rotateY(180deg); 24 | } 25 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-item/project-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { ProjectItemComponent } from './project-item.component'; 4 | 5 | describe('ProjectItemComponent', () => { 6 | let component: ProjectItemComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ProjectItemComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(ProjectItemComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/modules/home/page/project-item/project-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | import { Project } from '@data/schema/project'; 4 | 5 | @Component({ 6 | selector: 'app-project-item', 7 | templateUrl: './project-item.component.html', 8 | styleUrls: ['./project-item.component.scss'] 9 | }) 10 | export class ProjectItemComponent { 11 | @Input() project: Project; 12 | 13 | flipped = false; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/modules/home/project-resolver.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | ActivatedRouteSnapshot, 4 | Resolve, 5 | Router, 6 | RouterStateSnapshot 7 | } from '@angular/router'; 8 | import { Observable } from 'rxjs'; 9 | import { catchError } from 'rxjs/operators'; 10 | 11 | import { Project } from '@data/schema/project'; 12 | import { ProjectService } from '@data/service/project.service'; 13 | 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class ProjectResolver implements Resolve { 18 | constructor(private projectService: ProjectService, private router: Router) {} 19 | 20 | resolve( 21 | route: ActivatedRouteSnapshot, 22 | state: RouterStateSnapshot 23 | ): Observable { 24 | return this.projectService 25 | .getSingle(route.params['id']) 26 | .pipe(catchError(() => this.router.navigateByUrl('/'))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/shared/README.md: -------------------------------------------------------------------------------- 1 | Shared Module 2 | ============= 3 | 4 | This module is part of the structure which `angular-folder-structure` 5 | recommends. For more information about this module see the 6 | [documentation](https://angular-folder-structure.readthedocs.io/en/latest/shared.html). 7 | 8 | -------------------------------------------------------------------------------- /src/app/shared/component/control-messages/control-messages.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ errorMessage }} 3 | 4 | -------------------------------------------------------------------------------- /src/app/shared/component/control-messages/control-messages.component.scss: -------------------------------------------------------------------------------- 1 | .errorMessage { 2 | color: #a94442; 3 | margin-top: 1rem; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/shared/component/control-messages/control-messages.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { ControlMessagesComponent } from './control-messages.component'; 4 | 5 | describe('ControlMessagesComponent', () => { 6 | let component: ControlMessagesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ControlMessagesComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(ControlMessagesComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/shared/component/control-messages/control-messages.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { UntypedFormControl, AbstractControl } from '@angular/forms'; 3 | import { ValidationService } from '../../service/validation.service'; 4 | 5 | @Component({ 6 | selector: 'app-control-messages', 7 | templateUrl: './control-messages.component.html', 8 | styleUrls: ['./control-messages.component.scss'] 9 | }) 10 | export class ControlMessagesComponent { 11 | @Input() control: UntypedFormControl | AbstractControl; 12 | @Input() labelName?: string; 13 | 14 | get errorMessage(): boolean { 15 | for (const propertyName in this.control.errors) { 16 | if ( 17 | this.control.errors.hasOwnProperty(propertyName) && 18 | this.control.touched 19 | ) { 20 | return ValidationService.getValidationErrorMessage( 21 | propertyName, 22 | this.control.errors[propertyName], 23 | this.labelName 24 | ); 25 | } 26 | } 27 | 28 | return undefined; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/shared/component/spinner/spinner.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Henter data

3 |
-------------------------------------------------------------------------------- /src/app/shared/component/spinner/spinner.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/app/shared/component/spinner/spinner.component.scss -------------------------------------------------------------------------------- /src/app/shared/component/spinner/spinner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { SpinnerComponent } from './spinner.component'; 4 | 5 | describe('SpinnerComponent', () => { 6 | let component: SpinnerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach( 10 | waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [SpinnerComponent] 13 | }).compileComponents(); 14 | }) 15 | ); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(SpinnerComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/shared/component/spinner/spinner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-spinner', 5 | templateUrl: './spinner.component.html', 6 | styleUrls: ['./spinner.component.scss'] 7 | }) 8 | export class SpinnerComponent { 9 | @Input() public isLoading = false; 10 | @Input() public message: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shared/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { MatButtonModule } from '@angular/material/button'; 4 | import { MatToolbarModule } from '@angular/material/toolbar'; 5 | import { MatMenuModule } from '@angular/material/menu'; 6 | import { MatSelectModule } from '@angular/material/select'; 7 | import { MatTabsModule } from '@angular/material/tabs'; 8 | import { MatCheckboxModule } from '@angular/material/checkbox'; 9 | import { MatListModule } from '@angular/material/list'; 10 | import { MatIconModule } from '@angular/material/icon'; 11 | import { MatNativeDateModule } from '@angular/material/core'; 12 | import { MatSidenavModule } from '@angular/material/sidenav'; 13 | import { MatInputModule } from '@angular/material/input'; 14 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 15 | import { MatChipsModule } from '@angular/material/chips'; 16 | import { MatCardModule } from '@angular/material/card'; 17 | import { MatSlideToggleModule } from '@angular/material/slide-toggle'; 18 | import { MatTooltipModule } from '@angular/material/tooltip'; 19 | import { MatSnackBarModule } from '@angular/material/snack-bar'; 20 | import { MatDividerModule } from '@angular/material/divider'; 21 | import { MatDatepickerModule } from '@angular/material/datepicker'; 22 | 23 | @NgModule({ 24 | declarations: [], 25 | imports: [ 26 | MatButtonModule, 27 | MatToolbarModule, 28 | MatMenuModule, 29 | MatSelectModule, 30 | MatTabsModule, 31 | MatInputModule, 32 | MatProgressSpinnerModule, 33 | MatChipsModule, 34 | MatSidenavModule, 35 | MatCheckboxModule, 36 | MatCardModule, 37 | MatListModule, 38 | MatIconModule, 39 | MatTooltipModule, 40 | MatSnackBarModule, 41 | MatSlideToggleModule, 42 | MatDividerModule, 43 | MatDatepickerModule, 44 | MatNativeDateModule 45 | ], 46 | exports: [ 47 | MatButtonModule, 48 | MatToolbarModule, 49 | MatMenuModule, 50 | MatSelectModule, 51 | MatTabsModule, 52 | MatInputModule, 53 | MatProgressSpinnerModule, 54 | MatChipsModule, 55 | MatSidenavModule, 56 | MatCheckboxModule, 57 | MatCardModule, 58 | MatListModule, 59 | MatIconModule, 60 | MatTooltipModule, 61 | MatSnackBarModule, 62 | MatSlideToggleModule, 63 | MatDividerModule, 64 | MatDatepickerModule, 65 | MatNativeDateModule 66 | ] 67 | }) 68 | export class MaterialModule {} 69 | -------------------------------------------------------------------------------- /src/app/shared/service/validation.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { inject, TestBed } from '@angular/core/testing'; 2 | 3 | import { ValidationService } from './validation.service'; 4 | 5 | describe('ValidationService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [ValidationService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([ValidationService], (service: ValidationService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/shared/service/validation.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { AbstractControl } from '@angular/forms'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class ValidationService { 8 | public static getValidationErrorMessage(validatorName: string, validatorValue?: any, labelName?: string): any { 9 | const config = { 10 | required: `Field is required.`, 11 | invalidPassword: 'Invalid password. Password must be at least 6 characters long, and contain a number.', 12 | maxlength: `The field can't contain more than ${validatorValue.requiredLength} characters.`, 13 | minlength: `The field must contain atleast ${validatorValue.requiredLength} characters.` 14 | }; 15 | 16 | return config[validatorName]; 17 | } 18 | 19 | public static passwordValidator(control: AbstractControl): any { 20 | if (!control.value) { return; } 21 | 22 | // {6,100} - Assert password is between 6 and 100 characters 23 | // (?=.*[0-9]) - Assert a string has at least one number 24 | // (?!.*\s) - Spaces are not allowed 25 | return (control.value.match(/^(?=.*\d)(?=.*[a-zA-Z!@#$%^&*])(?!.*\s).{6,100}$/)) ? '' : { invalidPassword: true }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | import { MaterialModule } from './material.module'; 7 | 8 | import { 9 | FontAwesomeModule, 10 | FaIconLibrary 11 | } from '@fortawesome/angular-fontawesome'; 12 | import { 13 | faPlus, 14 | faEdit, 15 | faTrash, 16 | faTimes, 17 | faCaretUp, 18 | faCaretDown, 19 | faExclamationTriangle, 20 | faFilter, 21 | faTasks, 22 | faCheck, 23 | faSquare, 24 | faLanguage, 25 | faPaintBrush, 26 | faLightbulb, 27 | faWindowMaximize, 28 | faStream, 29 | faBook, 30 | faUserCircle, 31 | faAsterisk 32 | } from '@fortawesome/free-solid-svg-icons'; 33 | import { faMediumM, faGithub } from '@fortawesome/free-brands-svg-icons'; 34 | 35 | import { ControlMessagesComponent } from './component/control-messages/control-messages.component'; 36 | import { SpinnerComponent } from './component/spinner/spinner.component'; 37 | 38 | @NgModule({ 39 | imports: [ 40 | CommonModule, 41 | FormsModule, 42 | ReactiveFormsModule, 43 | RouterModule, 44 | FontAwesomeModule 45 | ], 46 | declarations: [ControlMessagesComponent, SpinnerComponent], 47 | exports: [ 48 | CommonModule, 49 | FormsModule, 50 | ReactiveFormsModule, 51 | RouterModule, 52 | MaterialModule, 53 | FontAwesomeModule, 54 | ControlMessagesComponent, 55 | SpinnerComponent 56 | ] 57 | }) 58 | export class SharedModule { 59 | constructor(faIconLibrary: FaIconLibrary) { 60 | faIconLibrary.addIcons( 61 | faGithub, 62 | faMediumM, 63 | faPlus, 64 | faEdit, 65 | faTrash, 66 | faTimes, 67 | faCaretUp, 68 | faCaretDown, 69 | faExclamationTriangle, 70 | faFilter, 71 | faTasks, 72 | faCheck, 73 | faSquare, 74 | faLanguage, 75 | faPaintBrush, 76 | faLightbulb, 77 | faWindowMaximize, 78 | faStream, 79 | faBook, 80 | faUserCircle, 81 | faAsterisk 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/api/auth.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/api/auth.json -------------------------------------------------------------------------------- /src/assets/api/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "link": "/", 4 | "title": "Online e-merchant management system - attaching integration with the iOS" 5 | }, 6 | { 7 | "link": "/", 8 | "title": "Notes on pipeline upgrade" 9 | }, 10 | { 11 | "link": "/", 12 | "title": "Assessment report for merchant account" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/assets/images/bg-screenshot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/images/bg-screenshot.PNG -------------------------------------------------------------------------------- /src/assets/images/mountain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/images/mountain.png -------------------------------------------------------------------------------- /src/assets/images/winter-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/images/winter-photo.jpg -------------------------------------------------------------------------------- /src/assets/images/winter-photo2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/images/winter-photo2.jpg -------------------------------------------------------------------------------- /src/assets/images/winter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/images/winter.jpg -------------------------------------------------------------------------------- /src/assets/scss/_base.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/scss/_base.scss -------------------------------------------------------------------------------- /src/assets/scss/_colors.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/assets/scss/_colors.scss -------------------------------------------------------------------------------- /src/environments/.env.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'npm_package_version': '1.0.0' 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | // `.env.ts` is generated by the `npm run env` command 2 | import env from './.env'; 3 | 4 | export const environment = { 5 | production: true, 6 | version: env.npm_package_version, 7 | serverUrl: '/api', 8 | envName: 'PROD' 9 | }; 10 | -------------------------------------------------------------------------------- /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 | // `.env.ts` is generated by the `npm run env` command 6 | import env from './.env'; 7 | 8 | export const environment = { 9 | production: false, 10 | version: env.npm_package_version + '-dev', 11 | serverUrl: '/api', 12 | envName: 'DEV' 13 | }; 14 | 15 | /* 16 | * In development mode, to ignore zone related error stack frames such as 17 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 18 | * import the following file, but please comment it out in production mode 19 | * because it will have performance impact when throw error 20 | */ 21 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 22 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathisGarberg/angular-folder-structure/9d554b33144f2a90f56f80e90cd65c32be0424d5/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Folder Structure 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | var isWatch = config.buildWebpack.options.watch; 6 | config.set({ 7 | basePath: '', 8 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 9 | plugins: [ 10 | require('karma-jasmine'), 11 | require('karma-chrome-launcher'), 12 | require('karma-jasmine-html-reporter'), 13 | require('karma-spec-reporter'), 14 | require('karma-coverage-istanbul-reporter'), 15 | require('@angular-devkit/build-angular/plugins/karma') 16 | ], 17 | client: { 18 | clearContext: false // leave Jasmine Spec Runner output visible in browser 19 | }, 20 | coverageIstanbulReporter: { 21 | dir: require('path').join(__dirname, '../coverage'), 22 | reports: ['html', 'lcovonly'], 23 | fixWebpackSourcePaths: true 24 | }, 25 | reporters: ['spec'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | customLaunchers: { 32 | ChromeTravisCi: { 33 | base: 'Chrome', 34 | flags: ['--no-sandbox'] 35 | } 36 | }, 37 | browserNoActivityTimeout: 50000, 38 | singleRun: !isWatch 39 | }); 40 | }; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch(err => console.log(err)); 14 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. 3 | */ 4 | import '@angular/localize/init'; 5 | /** 6 | * This file includes polyfills needed by Angular and is loaded before the app. 7 | * You can add your own extra polyfills to this file. 8 | * 9 | * This file is divided into 2 sections: 10 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 11 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 12 | * file. 13 | * 14 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 15 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 16 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 17 | * 18 | * Learn more in https://angular.io/guide/browser-support 19 | */ 20 | 21 | /*************************************************************************************************** 22 | * BROWSER POLYFILLS 23 | */ 24 | 25 | /** IE10 and IE11 requires the following for the Reflect API. */ 26 | import 'core-js/es6/reflect'; 27 | 28 | /** Evergreen browsers require these. **/ 29 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 30 | import 'core-js/es7/reflect'; // Run `npm install --save web-animations-js`. 31 | 32 | /** 33 | * By default, zone.js will patch all possible macroTask and DomEvents 34 | * user can disable parts of macroTask/DomEvents patch by setting following flags 35 | */ 36 | 37 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 38 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 39 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 40 | 41 | /* 42 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 43 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 44 | */ 45 | // (window as any).__Zone_enable_cross_context_check = true; 46 | 47 | /*************************************************************************************************** 48 | * Zone JS is required by default for Angular itself. 49 | */ 50 | import 'zone.js'; // Included with Angular CLI. 51 | 52 | /*************************************************************************************************** 53 | * APPLICATION IMPORTS 54 | */ 55 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @use '@angular/material' as mat; 2 | @import '~bootstrap/scss/bootstrap'; 3 | 4 | @import '@angular/material/theming'; 5 | 6 | $custom-typography: mat.define-typography-config( 7 | $font-family: 'Open-sans, Arial, sans-serif' 8 | ); 9 | 10 | @import './styles/styles-variables'; 11 | @import './styles/style-reset'; 12 | @import './styles/styles-reset.scss-theme'; 13 | 14 | @include mat.core(); 15 | 16 | @import './themes/black-theme.scss'; 17 | @import './themes/light-theme.scss'; 18 | 19 | @import 'app/modules/home/page/project-item/project-container.component.scss-theme.scss'; 20 | 21 | @mixin custom-components-theme($theme) { 22 | @include my-styles-reset-theme($theme); 23 | @include my-project-container-component-theme($theme); 24 | } 25 | 26 | .afs-light-theme { 27 | @include mat.all-component-themes($afs-light-theme); 28 | @include custom-components-theme($afs-light-theme); 29 | } 30 | 31 | .afs-dark-theme { 32 | @include mat.all-component-themes($afs-black-theme); 33 | @include custom-components-theme($afs-black-theme); 34 | } 35 | -------------------------------------------------------------------------------- /src/styles/README.md: -------------------------------------------------------------------------------- 1 | Styles Directory 2 | ================ 3 | 4 | This directory is part of the structure which `angular-folder-structure` 5 | recommends. For more information about this directory see the 6 | [documentation](https://angular-folder-structure.readthedocs.io/en/latest/styles.html). 7 | -------------------------------------------------------------------------------- /src/styles/style-reset.scss: -------------------------------------------------------------------------------- 1 | * { 2 | &:active, 3 | :focus { 4 | outline: none !important; 5 | } 6 | } 7 | 8 | code { 9 | white-space: nowrap; 10 | border-radius: 10px; 11 | padding: 0 8px 1px 8px; 12 | } 13 | 14 | .mat-menu-content { 15 | fa-icon { 16 | position: relative; 17 | top: 2px; 18 | left: 4px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/styles-reset.scss-theme.scss: -------------------------------------------------------------------------------- 1 | @use '@angular/material' as mat; 2 | @import '@angular/material/theming'; 3 | 4 | @mixin my-styles-reset-theme($theme) { 5 | $primary: map-get($theme, primary); 6 | $accent: map-get($theme, accent); 7 | $foreground: map-get($theme, foreground); 8 | 9 | a { 10 | color: mat.get-color-from-palette($foreground, text); 11 | 12 | &:hover { 13 | color: mat.get-color-from-palette($accent); 14 | } 15 | } 16 | 17 | code { 18 | color: mat.get-color-from-palette($accent, lighter-contrast); 19 | background-color: mat.get-color-from-palette($accent, lighter); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/styles/styles-variables.scss: -------------------------------------------------------------------------------- 1 | @import '@angular/material/theming'; 2 | 3 | $fa-font-path: '../node_modules/@fortawesome/fontawesome-free-webfonts/webfonts'; 4 | 5 | $link-hover-decoration: none; 6 | $label-margin-bottom: 0; 7 | 8 | $grid-breakpoints: ( 9 | xs: 0, 10 | sm: 576px, 11 | md: 768px, 12 | lg: 992px, 13 | xl: 1200px 14 | ); 15 | -------------------------------------------------------------------------------- /src/styles/themes/black-theme.scss: -------------------------------------------------------------------------------- 1 | @use '@angular/material' as mat; 2 | $afs-dark-primary: mat.define-palette(mat.$grey-palette, 700, 300, 900); 3 | $afs-dark-accent: mat.define-palette(mat.$blue-grey-palette, 400); 4 | $afs-dark-warn: mat.define-palette(mat.$red-palette, 500); 5 | 6 | $afs-black-theme: mat.define-dark-theme( 7 | $afs-dark-primary, 8 | $afs-dark-accent, 9 | $afs-dark-warn 10 | ); 11 | -------------------------------------------------------------------------------- /src/styles/themes/light-theme.scss: -------------------------------------------------------------------------------- 1 | @use '@angular/material' as mat; 2 | $afs-light-primary: mat.define-palette(mat.$grey-palette, 200, 500, 300); 3 | $afs-light-accent: mat.define-palette(mat.$brown-palette, 200); 4 | $afs-light-warn: mat.define-palette(mat.$deep-orange-palette, 200); 5 | 6 | $afs-light-theme: mat.define-light-theme( 7 | $afs-light-primary, 8 | $afs-light-accent, 9 | $afs-light-warn 10 | ); 11 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting(), { 16 | teardown: { destroyAfterEach: false } 17 | } 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "", 5 | "outDir": "../out-tsc/app", 6 | "types": ["node"] 7 | }, 8 | "files": ["main.ts", "polyfills.ts"], 9 | "include": ["src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "", 5 | "outDir": "../out-tsc/spec", 6 | "types": ["jasmine", "node"] 7 | }, 8 | "files": ["test.ts", "polyfills.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | 7 | declare module '*'; 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | "module": "esnext", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "emitDecoratorMetadata": true, 13 | "experimentalDecorators": true, 14 | "target": "es5", 15 | "typeRoots": ["node_modules/@types"], 16 | "paths": { 17 | "@app/*": ["app/*"], 18 | "@core/*": ["app/core/*"], 19 | "@shared/*": ["app/shared/*"], 20 | "@env": ["environments/environment"], 21 | "@modules/*": ["app/modules/*"], 22 | "@data/*": ["app/data/*"] 23 | }, 24 | "lib": ["es2017", "dom"] 25 | }, 26 | "angularCompilerOptions": { 27 | "strictTemplates": true, 28 | "strictInjectionParameters": true 29 | } 30 | } 31 | --------------------------------------------------------------------------------