├── .browserslistrc ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── core │ │ ├── core.module.ts │ │ ├── http │ │ │ ├── api-prefix.interceptor.spec.ts │ │ │ ├── api-prefix.interceptor.ts │ │ │ ├── error-handler.interceptor.spec.ts │ │ │ ├── error-handler.interceptor.ts │ │ │ ├── http-request.service.spec.ts │ │ │ └── http-request.service.ts │ │ ├── logger.service.spec.ts │ │ ├── logger.service.ts │ │ ├── route-reusable-strategy.ts │ │ ├── until-destroyed.spec.ts │ │ └── until-destroyed.ts │ ├── functional │ │ └── functional.module.ts │ └── shared │ │ ├── header │ │ ├── header.component.css │ │ ├── header.component.html │ │ ├── header.component.spec.ts │ │ └── header.component.ts │ │ ├── landing │ │ ├── landing.component.css │ │ ├── landing.component.html │ │ ├── landing.component.spec.ts │ │ └── landing.component.ts │ │ ├── leftpane │ │ ├── leftpane.component.css │ │ ├── leftpane.component.html │ │ ├── leftpane.component.spec.ts │ │ └── leftpane.component.ts │ │ ├── loader │ │ ├── loader.component.css │ │ ├── loader.component.html │ │ ├── loader.component.spec.ts │ │ ├── loader.component.ts │ │ └── service │ │ │ ├── loader.interceptor.spec.ts │ │ │ ├── loader.interceptor.ts │ │ │ ├── loader.service.spec.ts │ │ │ └── loader.service.ts │ │ ├── services │ │ ├── commom-service.spec.ts │ │ └── common.service.ts │ │ └── shared.module.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css └── test.ts ├── stryker.conf.json ├── tsconfig.app.json ├── tsconfig.base.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Application Web 2 | 3 | This project is to help doing basic setup of Angular Applications. 4 | 5 | ## Functionality of the Application 6 | 7 | This application loads a basic page with Header, Footer and left pane. 8 | Below are the common components avaiable as part of this application 9 | 10 | 1. ErrorHandlerInterceptor - To handle all the error in http response 11 | 2. ApiPrefixInterceptor - To handle the api call for the application using the designated server url 12 | 3. HttpRequestService - To handle all the http request using a common service (helps us decoupling business logic from Data access) 13 | 4. logger service - To handle logging using common logic for the whole code. 14 | 5. until-destroyed - RxJS operator that unsubscribe from observables on destory 15 | 6. RouteReusableStrategy - A route strategy allowing for explicit route reuse. 16 | 7. loader - A common loader for whole application 17 | 18 | ## Development and Building at local 19 | 20 | This is an Angular application, and built using Angular CLI. Below are the required dependencies for development 21 | 22 | 1. Node JS version > 12 23 | 2. Angular CLI , you can install it by running npm install -g @angular/cli 24 | 3. Stryker for Mutation Testing, you can install it by running npm install -g stryker-cli 25 | 4. JSCPD for duplicate block check, you can install it by running npm install -g jscpd 26 | 6. Documentation for the application, you can install it by running npm install -g @compodoc/compodoc 27 | 28 | Post installing all the required dependencies listed above. You need to install node module dependencies by running the command npm install from root folder. 29 | Once done to run the application you have to run ng serve for a dev server and watch for your changes in your favorite IDE. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files. 30 | 31 | ## Code scaffolding 32 | 33 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 34 | 35 | ## Build 36 | 37 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 38 | 39 | ## Running unit tests 40 | 41 | To Run unit tests use ng test command it automatically watches changes for source code 42 | To Run unit tests with coverage ng test --code-coverage command it automatically watches changes for source code 43 | To Run unit tests with out watch and progression ng test --watch false 44 | To Run unit tests with coverage with out watch and progression ng test --code-coverage --watch false 45 | 46 | ## Running Static Analysis 47 | 48 | To run all lint use ng lint command 49 | To run duplicate check use jscpd src --min-tokens=45 command 50 | 51 | ## Running Mutation Testing 52 | 53 | To perform mutation analysis using Stryker 54 | Use stryker run command from the root of the project 55 | 56 | ## Documentation 57 | 58 | Compodoc is used for detail documentation for the application. 59 | To generate documentation npm run documentation 60 | To generate minimal documetation npm run documentationminimal 61 | 62 | ## Running end-to-end tests 63 | 64 | Due to time constraints E2E tests are not yet complete but it is configured. So please feel free to contribute to it. 65 | To Run ng e2e to execute the end-to-end tests via Protractor. 66 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-basics": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/angular-basics", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": true, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true, 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "2mb", 51 | "maximumError": "5mb" 52 | }, 53 | { 54 | "type": "anyComponentStyle", 55 | "maximumWarning": "6kb", 56 | "maximumError": "10kb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "angular-basics:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "angular-basics:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "angular-basics:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "main": "src/test.ts", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "tsconfig.spec.json", 85 | "karmaConfig": "karma.conf.js", 86 | "assets": [ 87 | "src/favicon.ico", 88 | "src/assets" 89 | ], 90 | "styles": [ 91 | "src/styles.css" 92 | ], 93 | "scripts": [] 94 | } 95 | }, 96 | "lint": { 97 | "builder": "@angular-devkit/build-angular:tslint", 98 | "options": { 99 | "tsConfig": [ 100 | "tsconfig.app.json", 101 | "tsconfig.spec.json", 102 | "e2e/tsconfig.json" 103 | ], 104 | "exclude": [ 105 | "**/node_modules/**" 106 | ] 107 | } 108 | }, 109 | "e2e": { 110 | "builder": "@angular-devkit/build-angular:protractor", 111 | "options": { 112 | "protractorConfig": "e2e/protractor.conf.js", 113 | "devServerTarget": "angular-basics:serve" 114 | }, 115 | "configurations": { 116 | "production": { 117 | "devServerTarget": "angular-basics:serve:production" 118 | } 119 | } 120 | } 121 | } 122 | }}, 123 | "defaultProject": "angular-basics" 124 | } 125 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('angular-basics app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/angular-basics'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | captureTimeout: 6000000, 29 | browserDisconnectTolerance: 3, 30 | browserDisconnectTimeout: 6000000, 31 | browserNoActivityTimeout: 6000000, 32 | "customLaunchers": { 33 | "ChromeHeadless": { 34 | "base": "Chrome", 35 | "flags": [ 36 | "--headless", 37 | "--disable-gpu", 38 | "--no-sandbox", 39 | "--remote-debugging-port=9222" 40 | ] 41 | } 42 | }, 43 | browsers: ['ChromeHeadless'], 44 | singleRun: false, 45 | restartOnFileChange: true 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-basics", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e", 11 | "coverage": "ng test --code-coverage", 12 | "duplicate": "jscpd src --min-tokens=25", 13 | "documentation": "compodoc -p src/tsconfig.app.json --disableSourceCode --disableDomTree --disableTemplateTab --disableStyleTab", 14 | "documentationminimal": "compodoc -p src/tsconfig.app.json --minimal" 15 | }, 16 | "private": true, 17 | "dependencies": { 18 | "@angular/animations": "~10.0.4", 19 | "@angular/common": "~10.0.4", 20 | "@angular/compiler": "~10.0.4", 21 | "@angular/core": "~10.0.4", 22 | "@angular/forms": "~10.0.4", 23 | "@angular/platform-browser": "~10.0.4", 24 | "@angular/platform-browser-dynamic": "~10.0.4", 25 | "@angular/router": "~10.0.4", 26 | "ng-mocks": "^11.10.1", 27 | "rxjs": "~6.5.5", 28 | "tslib": "^2.0.0", 29 | "zone.js": "~0.10.3" 30 | }, 31 | "devDependencies": { 32 | "@angular-devkit/build-angular": "~0.1000.3", 33 | "@angular/cli": "~10.0.3", 34 | "@angular/compiler-cli": "~10.0.4", 35 | "@types/node": "^12.11.1", 36 | "@types/jasmine": "~3.5.0", 37 | "@types/jasminewd2": "~2.0.3", 38 | "codelyzer": "^6.0.0", 39 | "jasmine-core": "~3.5.0", 40 | "jasmine-spec-reporter": "~5.0.0", 41 | "karma": "~5.0.0", 42 | "karma-chrome-launcher": "~3.1.0", 43 | "karma-coverage-istanbul-reporter": "~3.0.2", 44 | "karma-jasmine": "~3.3.0", 45 | "karma-jasmine-html-reporter": "^1.5.0", 46 | "protractor": "~7.0.0", 47 | "ts-node": "~8.3.0", 48 | "tslint": "~6.1.0", 49 | "typescript": "~3.9.5" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = []; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes)], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule { } 11 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abhishek-Ankush-git/angular-basic/a7e868561fdcba4b325b47f57371720d4a5b1523/src/app/app.component.css -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { of } from 'rxjs'; 4 | import { AppComponent } from './app.component'; 5 | import { Logger } from './core/logger.service'; 6 | 7 | class MockLoggerService { 8 | log () { 9 | return of(true); 10 | } 11 | } 12 | 13 | describe('AppComponent', () => { 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [ 17 | RouterTestingModule 18 | ], 19 | declarations: [ 20 | AppComponent 21 | ], 22 | providers: [{provide: Logger, userClass: MockLoggerService}] 23 | }).compileComponents(); 24 | })); 25 | 26 | it('should create the app', () => { 27 | const fixture = TestBed.createComponent(AppComponent); 28 | const app = fixture.componentInstance; 29 | expect(app).toBeTruthy(); 30 | }); 31 | 32 | it(`should have as title 'angular-basics'`, () => { 33 | const fixture = TestBed.createComponent(AppComponent); 34 | const app = fixture.componentInstance; 35 | expect(app.title).toEqual('angular-basics'); 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, isDevMode, OnInit } from '@angular/core'; 2 | import { Logger } from './core/logger.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent implements OnInit { 10 | title = 'angular-basics'; 11 | 12 | public constructor(private _log: Logger) { 13 | } 14 | 15 | public ngOnInit() { 16 | if (!isDevMode()) { 17 | this._log.enableProductionMode(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { CoreModule } from './core/core.module'; 6 | import { SharedModule } from './shared/shared.module'; 7 | import { FunctionalModule } from './functional/functional.module'; 8 | import { AppComponent } from './app.component'; 9 | import { CommonModule } from '@angular/common'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent 14 | ], 15 | imports: [ 16 | CommonModule, 17 | BrowserModule, 18 | AppRoutingModule, 19 | CoreModule, 20 | SharedModule, 21 | FunctionalModule 22 | ], 23 | providers: [], 24 | bootstrap: [AppComponent] 25 | }) 26 | export class AppModule { } 27 | -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Optional, SkipSelf } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; 4 | import { RouteReuseStrategy, RouterModule } from '@angular/router'; 5 | import { ApiPrefixInterceptor } from './http/api-prefix.interceptor'; 6 | import { ErrorHandlerInterceptor } from './http/error-handler.interceptor'; 7 | import { RouteReusableStrategy } from './route-reusable-strategy'; 8 | import { HttpRequestService } from './http/http-request.service'; 9 | 10 | @NgModule({ 11 | declarations: [], 12 | imports: [ 13 | CommonModule, HttpClientModule, RouterModule 14 | ], 15 | providers: [ 16 | { 17 | provide: HTTP_INTERCEPTORS, 18 | useClass: ApiPrefixInterceptor, 19 | multi: true 20 | }, 21 | { 22 | provide: HTTP_INTERCEPTORS, 23 | useClass: ErrorHandlerInterceptor, 24 | multi: true, 25 | }, 26 | { 27 | provide: RouteReuseStrategy, 28 | useClass: RouteReusableStrategy, 29 | }, 30 | HttpRequestService 31 | ], 32 | exports: [] 33 | }) 34 | export class CoreModule { 35 | constructor(@Optional() @SkipSelf() parentModule: CoreModule) { 36 | // Import guard 37 | if (parentModule) { 38 | throw new Error(`${parentModule} has already been loaded. Import Core module in the AppModule only.`); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/core/http/api-prefix.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 4 | import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; 5 | 6 | import { environment } from '../../../environments/environment'; 7 | import { ApiPrefixInterceptor } from './api-prefix.interceptor'; 8 | 9 | describe('ApiPrefixInterceptor', () => { 10 | let http: HttpClient; 11 | let httpMock: HttpTestingController; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [HttpClientTestingModule], 16 | providers: [{ 17 | provide: HTTP_INTERCEPTORS, 18 | useClass: ApiPrefixInterceptor, 19 | multi: true 20 | }] 21 | }); 22 | http = TestBed.inject(HttpClient); 23 | httpMock = TestBed.inject(HttpTestingController as Type); 24 | }); 25 | 26 | afterEach(() => { 27 | httpMock.verify(); 28 | }); 29 | 30 | it('should prepend environment.serverUrl to the request url', () => { 31 | // Act 32 | http.get('/toto').subscribe(); 33 | 34 | // Assert 35 | httpMock.expectOne({ url: environment.serverUrl + '/toto' }); 36 | }); 37 | 38 | it('should not prepend environment.serverUrl to request url', () => { 39 | // Act 40 | http.get('hTtPs://domain.com/toto').subscribe(); 41 | 42 | // Assert 43 | httpMock.expectOne({ url: 'hTtPs://domain.com/toto' }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/app/core/http/api-prefix.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, isDevMode } from '@angular/core'; 2 | import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { environment } from '../../../environments/environment'; 6 | 7 | /** 8 | * Prefixes all requests not starting with `http[s]` with `environment.serverUrl`. 9 | */ 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class ApiPrefixInterceptor implements HttpInterceptor { 14 | 15 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 16 | if (!/^(http|https):/i.test(request.url) && isDevMode()) { 17 | request = request.clone({ url: environment.serverUrl + request.url }); 18 | } 19 | return next.handle(request); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/core/http/error-handler.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 4 | import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; 5 | 6 | import { ErrorHandlerInterceptor } from './error-handler.interceptor'; 7 | import { Logger } from '../logger.service'; 8 | import { HttpRequestService } from './http-request.service'; 9 | 10 | describe('ErrorHandlerInterceptor', () => { 11 | let errorHandlerInterceptor: ErrorHandlerInterceptor; 12 | let http: HttpClient; 13 | let httpMock: HttpTestingController; 14 | 15 | function createInterceptor() { 16 | errorHandlerInterceptor = new ErrorHandlerInterceptor(TestBed.get(Logger)); 17 | return errorHandlerInterceptor; 18 | } 19 | 20 | beforeEach(() => { 21 | TestBed.configureTestingModule({ 22 | imports: [HttpClientTestingModule], 23 | providers: [ 24 | { 25 | provide: HTTP_INTERCEPTORS, 26 | useFactory: createInterceptor, 27 | multi: true, 28 | }, HttpRequestService 29 | ], 30 | }); 31 | 32 | http = TestBed.get(HttpClient); 33 | httpMock = TestBed.get(HttpTestingController as Type); 34 | }); 35 | 36 | afterEach(() => { 37 | httpMock.verify(); 38 | }); 39 | 40 | it('should create instance for the error handler', () => { 41 | expect(createInterceptor()).toBeTruthy(); 42 | }); 43 | 44 | it('should catch error and call error handler', () => { 45 | spyOn(ErrorHandlerInterceptor.prototype as any, 'errorHandler').and.callThrough(); 46 | http.get('/toto').subscribe( 47 | () => fail('should error'), 48 | () => { 49 | expect((ErrorHandlerInterceptor.prototype as any).errorHandler).toHaveBeenCalled(); 50 | } 51 | ); 52 | 53 | httpMock.expectOne({url: '/toto'}).flush(null, { 54 | status: 404, 55 | statusText: 'Not found' 56 | }); 57 | 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/app/core/http/error-handler.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http'; 3 | import { Observable, throwError, of } from 'rxjs'; 4 | import { catchError, retryWhen, take, concat, mergeMap } from 'rxjs/operators'; 5 | 6 | import { Logger } from '../logger.service'; 7 | 8 | // const log = new Logger('ErrorHandlerInterceptor'); 9 | 10 | export class HttpResponseWithMessage extends HttpResponse { 11 | public message?: string; 12 | } 13 | /** 14 | * Adds a default error handler to all requests. 15 | */ 16 | @Injectable({ 17 | providedIn: 'root', 18 | }) 19 | export class ErrorHandlerInterceptor implements HttpInterceptor { 20 | 21 | public constructor(private _log: Logger) { } 22 | 23 | /** 24 | * Intercept API error response 25 | * @param {HttpRequest} request 26 | * @param {HttpHandler} next 27 | * @returns Observable 28 | */ 29 | public intercept(request: HttpRequest, next: HttpHandler): Observable> { 30 | return next.handle(request).pipe( 31 | retryWhen(error => { 32 | return error.pipe( 33 | mergeMap((errorData: any) => { 34 | // retry only if error status is 500 35 | return errorData.status === 500 ? of(errorData) : throwError(errorData); 36 | }), 37 | take(2), 38 | concat(throwError(error))); 39 | }), 40 | catchError((error) => this.errorHandler(error))); 41 | } 42 | 43 | /** 44 | * Customize the default error handler here if needed 45 | * @param response 46 | * @returns handler 47 | */ 48 | private errorHandler(response: HttpResponseWithMessage): Observable { 49 | const errormessage = `url: ${response.url} status: ${response.status} statusText: ${response.statusText} body: ${response.body}`; 50 | this._log.error('API error', errormessage); 51 | throw response; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/core/http/http-request.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpRequestService } from './http-request.service'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 4 | import { HttpClient } from '@angular/common/http'; 5 | import { Type } from '@angular/core'; 6 | 7 | describe('http-request-service', () => { 8 | let mockHttpRequestService: HttpRequestService; 9 | let http: HttpClient; 10 | let httpMock: HttpTestingController; 11 | let responseData: any; 12 | const mockResponseData = 'unittesting'; 13 | 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [HttpClientTestingModule], 17 | providers: [HttpClient] 18 | }); 19 | http = TestBed.get(HttpClient); 20 | httpMock = TestBed.get(HttpTestingController as Type); 21 | }); 22 | beforeEach(() => { 23 | mockHttpRequestService = new HttpRequestService(http); 24 | }); 25 | afterEach(() => { 26 | httpMock.verify(); 27 | }); 28 | 29 | it('should create an instance', () => { 30 | expect(new HttpRequestService(TestBed.get(HttpClient))).toBeTruthy(); 31 | }); 32 | 33 | it('should call and verify the get menthod', () => { 34 | mockHttpRequestService.get('/toto').toPromise().then((response) => { responseData = response; }); 35 | 36 | // Assert 37 | const req = httpMock.expectOne({ url: '/toto' }); 38 | req.flush(mockResponseData); 39 | }); 40 | 41 | it('should call and verify the post method', () => { 42 | mockHttpRequestService.post('/postrequest', {}).toPromise().then((response) => response); 43 | 44 | // Assert 45 | let req = httpMock.expectOne({ url: '/postrequest' }); 46 | req.flush(mockResponseData); 47 | expect(req.request.url).toBe('/postrequest'); 48 | expect(req.request.body).toEqual({}); 49 | 50 | mockHttpRequestService.post('/postrequest', {}, { responseType: 'text' }).toPromise().then((response) => { responseData = response; }); 51 | 52 | // Assert 53 | req = httpMock.expectOne({ url: '/postrequest' }); 54 | req.flush(mockResponseData); 55 | expect(req.request.url).toBe('/postrequest'); 56 | expect(req.request.body).toEqual({}); 57 | }); 58 | it('should call and verify the put method', () => { 59 | mockHttpRequestService.put('/putrequest', {}).toPromise().then((response) => response); 60 | 61 | // Assert 62 | let req = httpMock.expectOne({ url: '/putrequest' }); 63 | req.flush(mockResponseData); 64 | expect(req.request.url).toBe('/putrequest'); 65 | expect(req.request.body).toEqual({}); 66 | 67 | mockHttpRequestService.put('/putrequest', {}, { responseType: 'text' }).toPromise().then((response) => { responseData = response; }); 68 | 69 | // Assert 70 | req = httpMock.expectOne({ url: '/putrequest' }); 71 | req.flush(mockResponseData); 72 | expect(req.request.url).toBe('/putrequest'); 73 | expect(req.request.body).toEqual({}); 74 | }); 75 | it('should call and verify the delete method', () => { 76 | 77 | mockHttpRequestService.delete('/deleterequest', {}).toPromise().then((response) => { responseData = response; }); 78 | 79 | // Assert 80 | const req = httpMock.expectOne({ url: '/deleterequest' }); 81 | req.flush(mockResponseData); 82 | expect(req.request.url).toBe('/deleterequest'); 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /src/app/core/http/http-request.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class HttpRequestService { 9 | private _header: HttpHeaders = new HttpHeaders({ responseType: 'JSON' }); 10 | 11 | constructor(private _http: HttpClient) { } 12 | 13 | /** 14 | * Gets http request service 15 | * @param url 16 | * @param [header] 17 | * @returns get 18 | */ 19 | public get(url: string, header?: object): Observable { 20 | header = header ?? this._header; 21 | return this._http.get(url, header); 22 | } 23 | 24 | /** 25 | * Posts http request service 26 | * @param url 27 | * @param body 28 | * @param [header] 29 | * @returns post 30 | */ 31 | public post(url: string, body: object, header?: object): Observable { 32 | header = header ?? this._header; 33 | return this._http.post(url, body, header); 34 | } 35 | 36 | /** 37 | * Puts http request service 38 | * @param url 39 | * @param [body] 40 | * @param [header] 41 | * @returns put 42 | */ 43 | public put(url: string, body?: object, header?: object): Observable { 44 | header = header ?? this._header; 45 | return this._http.put(url, body, header); 46 | } 47 | 48 | /** 49 | * Deletes http request service 50 | * @param url 51 | * @param [header] 52 | * @param [requestBody] 53 | * @returns delete 54 | */ 55 | public delete(url: string, header?: object, requestBody?: object): Observable { 56 | const options = { 57 | headers: header === undefined ? this._header : new HttpHeaders(header as { [key: string]: string | string[] }), 58 | body: requestBody, 59 | }; 60 | return this._http.request('delete', url, options); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/core/logger.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 3 | import { Logger, LogLevel } from './logger.service'; 4 | 5 | describe('Logger-Service', () => { 6 | let mockLogger: Logger; 7 | const mocksource = 'UnitTest'; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [HttpClientTestingModule] 12 | }); 13 | }); 14 | 15 | beforeEach(() => { 16 | mockLogger = new Logger(); 17 | }); 18 | afterEach(() => { 19 | }); 20 | 21 | it('should create an instance', () => { 22 | expect(new Logger()).toBeTruthy(); 23 | }); 24 | 25 | it('should enable production Logs', () => { 26 | mockLogger.enableProductionMode(); 27 | expect(Logger.level).toEqual(LogLevel.Error); 28 | 29 | }); 30 | 31 | it('should validate all logging level', () => { 32 | const outputSpy = jasmine.createSpy('outputSpy'); 33 | Logger.outputs.push(outputSpy); 34 | 35 | mockLogger.debug(mocksource, 'debug Log'); 36 | mockLogger.info(mocksource, 'Info Log'); 37 | mockLogger.warn(mocksource, 'warning Log'); 38 | mockLogger.error(mocksource, 'error Log'); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /src/app/core/logger.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple logger system with the possibility of registering custom outputs. 3 | * 4 | * 4 different log levels are provided, with corresponding methods: 5 | * - debug : for debug information 6 | * - info : for informative status of the application (success, ...) 7 | * - warning : for non-critical errors that do not prevent normal application behavior 8 | * - error : for critical errors that prevent normal application behavior 9 | * 10 | * Example usage: 11 | * ``` 12 | * import { Logger } from 'app/core/logger.service'; 13 | * 14 | * const log = new Logger('myFile'); 15 | * ... 16 | * log.debug('something happened'); 17 | * ``` 18 | * 19 | * To disable debug and info logs in production, add this snippet to your root component: 20 | * ``` 21 | * export class AppComponent implements OnInit { 22 | * ngOnInit() { 23 | * if (environment.production) { 24 | * Logger.enableProductionMode(); 25 | * } 26 | * ... 27 | * } 28 | * } 29 | * 30 | * If you want to process logs through other outputs than console, you can add LogOutput functions to Logger.outputs. 31 | */ 32 | 33 | /** 34 | * The possible log levels. 35 | * LogLevel.Off is never emitted and only used with Logger.level property to disable logs. 36 | */ 37 | export enum LogLevel { 38 | Off = 0, 39 | Custom, 40 | Error, 41 | Warning, 42 | Info, 43 | Debug, 44 | } 45 | 46 | /** 47 | * Log output handler function. 48 | */ 49 | export type LogOutput = (source: string | undefined, level: LogLevel, ...objects: any[]) => void; 50 | 51 | import { Injectable } from '@angular/core'; 52 | @Injectable({ 53 | providedIn: 'root', 54 | }) 55 | export class Logger { 56 | constructor() { } 57 | /** 58 | * Current logging level. 59 | * Set it to LogLevel.Off to disable logs completely. 60 | */ 61 | static level = LogLevel.Debug; 62 | 63 | /** 64 | * Additional log outputs. 65 | */ 66 | static outputs: LogOutput[] = []; 67 | 68 | /** 69 | * Enables production mode. 70 | * Sets logging level to LogLevel.Warning. 71 | */ 72 | public enableProductionMode() { 73 | Logger.level = LogLevel.Error; 74 | } 75 | 76 | /** 77 | * Logs messages or objects with the debug level. 78 | * Works the same as console.log(). 79 | */ 80 | public debug(source: string, ...objects: any[]) { 81 | this.log(source, console.log, LogLevel.Debug, objects); 82 | } 83 | 84 | /** 85 | * Logs messages or objects with the info level. 86 | * Works the same as console.log(). 87 | */ 88 | public info(source: string, ...objects: any[]) { 89 | // tslint:disable-next-line:no-console 90 | this.log(source, console.info, LogLevel.Info, objects); 91 | } 92 | 93 | /** 94 | * Logs messages or objects with the warning level. 95 | * Works the same as console.log(). 96 | */ 97 | public warn(source: string, ...objects: any[]) { 98 | this.log(source, console.warn, LogLevel.Warning, objects); 99 | } 100 | 101 | /** 102 | * Logs messages or objects with the error level. 103 | * Works the same as console.log(). 104 | */ 105 | public error(source: string, ...objects: any[]) { 106 | this.log(source, console.error, LogLevel.Error, objects); 107 | } 108 | 109 | /** 110 | * @function log 111 | * @param source 112 | * @param func 113 | * @param level 114 | * @param objects 115 | * @description log messages and events 116 | */ 117 | private log(source: string, func: (...args: any[]) => void, level: LogLevel, objects: any[]) { 118 | const log = ['[' + source + ']'].concat(objects); 119 | if (level <= Logger.level) { 120 | func.apply(console, log); 121 | Logger.outputs.forEach((output) => output.apply(output, [source, level, objects])); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/app/core/route-reusable-strategy.ts: -------------------------------------------------------------------------------- 1 | import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'; 2 | import { Injectable } from '@angular/core'; 3 | import { Logger } from './logger.service'; 4 | 5 | /** 6 | * A route strategy allowing for explicit route reuse. 7 | * Used as a workaround for https://github.com/angular/angular/issues/18374 8 | * To reuse a given route, add `data: { reuse: true }` to the route definition. 9 | */ 10 | @Injectable() 11 | export class RouteReusableStrategy extends RouteReuseStrategy { 12 | 13 | private fileName = 'route reusable'; 14 | public constructor( private _log: Logger) { 15 | super(); 16 | } 17 | public shouldDetach(route: ActivatedRouteSnapshot): boolean { 18 | this._log.debug(this.fileName, route); 19 | return false; 20 | } 21 | 22 | public store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle | null): void { 23 | this._log.debug(this.fileName, route); 24 | this._log.debug(this.fileName, detachedTree); 25 | } 26 | 27 | public shouldAttach(route: ActivatedRouteSnapshot): boolean { 28 | this._log.debug(this.fileName, route); 29 | return false; 30 | } 31 | 32 | public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null { 33 | this._log.debug(this.fileName, route); 34 | return null; 35 | } 36 | 37 | public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { 38 | this._log.debug(this.fileName, future); 39 | this._log.debug(this.fileName, curr); 40 | return future.routeConfig === curr.routeConfig || future.data.reuse; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/core/until-destroyed.spec.ts: -------------------------------------------------------------------------------- 1 | import { OnInit, OnDestroy } from '@angular/core'; 2 | import { Subject, Subscription } from 'rxjs'; 3 | 4 | import { untilDestroyed } from './until-destroyed'; 5 | 6 | function createObserver() { 7 | return { 8 | next: jasmine.createSpy(), 9 | error: jasmine.createSpy(), 10 | complete: jasmine.createSpy(), 11 | }; 12 | } 13 | 14 | describe('untilDestroyed', () => { 15 | it('should not destroy other instances', () => { 16 | // Arrange 17 | const spy = createObserver(); 18 | const spy2 = createObserver(); 19 | 20 | class Test implements OnDestroy { 21 | obs!: Subscription; 22 | 23 | ngOnDestroy() {} 24 | 25 | subscribe(cb: any) { 26 | this.obs = new Subject().pipe(untilDestroyed(this)).subscribe(cb); 27 | } 28 | } 29 | 30 | // Act 31 | const component1 = new Test(); 32 | const component2 = new Test(); 33 | component1.subscribe(spy); 34 | component2.subscribe(spy2); 35 | component1.ngOnDestroy(); 36 | 37 | // Assert 38 | expect(spy.complete).toHaveBeenCalledTimes(1); 39 | expect(spy2.complete).not.toHaveBeenCalled(); 40 | component2.ngOnDestroy(); 41 | expect(spy2.complete).toHaveBeenCalledTimes(1); 42 | }); 43 | 44 | it('should work with multiple observables', () => { 45 | // Arrange 46 | const spy = createObserver(); 47 | const spy2 = createObserver(); 48 | const spy3 = createObserver(); 49 | 50 | class Test implements OnDestroy { 51 | obs = new Subject().pipe(untilDestroyed(this)).subscribe(spy); 52 | obs2 = new Subject().pipe(untilDestroyed(this)).subscribe(spy2); 53 | obs3 = new Subject().pipe(untilDestroyed(this)).subscribe(spy3); 54 | 55 | ngOnDestroy() {} 56 | } 57 | 58 | // Act 59 | const instance = new Test(); 60 | instance.ngOnDestroy(); 61 | 62 | // Assert 63 | expect(spy.complete).toHaveBeenCalledTimes(1); 64 | expect(spy2.complete).toHaveBeenCalledTimes(1); 65 | expect(spy3.complete).toHaveBeenCalledTimes(1); 66 | }); 67 | 68 | it('should work with classes that are not components', () => { 69 | // Arrange 70 | const spy = createObserver(); 71 | 72 | // Act 73 | class Test { 74 | obs = new Subject().pipe(untilDestroyed(this, 'destroy')).subscribe(spy); 75 | 76 | destroy() {} 77 | } 78 | 79 | // Assert 80 | const instance = new Test(); 81 | instance.destroy(); 82 | expect(spy.complete).toHaveBeenCalledTimes(1); 83 | }); 84 | 85 | it('should unsubscribe from anywhere', () => { 86 | // Arrange 87 | const spy = createObserver(); 88 | const spy2 = createObserver(); 89 | const spy3 = createObserver(); 90 | 91 | class LoginComponent implements OnInit, OnDestroy { 92 | dummy = new Subject().pipe(untilDestroyed(this)).subscribe(spy); 93 | 94 | constructor() { 95 | new Subject().pipe(untilDestroyed(this)).subscribe(spy2); 96 | } 97 | 98 | ngOnInit() { 99 | new Subject().pipe(untilDestroyed(this)).subscribe(spy3); 100 | } 101 | 102 | ngOnDestroy() {} 103 | } 104 | 105 | // Act 106 | const instance = new LoginComponent(); 107 | instance.ngOnInit(); 108 | instance.ngOnDestroy(); 109 | 110 | // Assert 111 | expect(spy.complete).toHaveBeenCalledTimes(1); 112 | expect(spy2.complete).toHaveBeenCalledTimes(1); 113 | expect(spy3.complete).toHaveBeenCalledTimes(1); 114 | }); 115 | 116 | it('should throw when destroy method doesnt exist', () => { 117 | // Arrange 118 | const spy = createObserver(); 119 | 120 | class LoginComponent { 121 | dummy = new Subject().pipe(untilDestroyed(this)).subscribe(spy); 122 | } 123 | 124 | // Assert 125 | expect(() => new LoginComponent()).toThrow(); 126 | }); 127 | 128 | it('should not throw when destroy method is implemented on super class', () => { 129 | // Arrange 130 | const spy = createObserver(); 131 | 132 | class A implements OnDestroy { 133 | ngOnDestroy() {} 134 | } 135 | 136 | class B extends A { 137 | dummy = new Subject().pipe(untilDestroyed(this)).subscribe(spy); 138 | } 139 | 140 | // Assert 141 | expect(() => new B()).not.toThrow(); 142 | }); 143 | 144 | it('should work with subclass', () => { 145 | // Arrange 146 | const spy = createObserver(); 147 | 148 | class Parent implements OnDestroy { 149 | ngOnDestroy() {} 150 | } 151 | 152 | class Child extends Parent { 153 | obs = new Subject().pipe(untilDestroyed(this)).subscribe(spy); 154 | 155 | constructor() { 156 | super(); 157 | } 158 | } 159 | 160 | // Assert 161 | const instance = new Child(); 162 | instance.ngOnDestroy(); 163 | expect(spy.complete).toHaveBeenCalledTimes(1); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /src/app/core/until-destroyed.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subject } from 'rxjs'; 2 | import { takeUntil } from 'rxjs/operators'; 3 | 4 | const untilDestroyedSymbol = Symbol('untilDestroyed'); 5 | 6 | /** 7 | * RxJS operator that unsubscribe from observables on destory. 8 | * Code forked from https://github.com/NetanelBasal/ngx-take-until-destroy 9 | * 10 | * IMPORTANT: Add the `untilDestroyed` operator as the last one to prevent leaks with intermediate observables in the 11 | * operator chain. 12 | * 13 | * @param instance The parent Angular component or object instance. 14 | * @param destroyMethodName The method to hook on (default: 'ngOnDestroy'). 15 | * @example 16 | * ``` 17 | * import { untilDestroyed } from '@core'; 18 | * 19 | * @Component({ 20 | * selector: 'app-example', 21 | * templateUrl: './example.component.html' 22 | * }) 23 | * export class ExampleComponent implements OnInit, OnDestroy { 24 | * ngOnInit() { 25 | * interval(1000) 26 | * .pipe(untilDestroyed(this)) 27 | * .subscribe(val => console.log(val)); 28 | * } 29 | * 30 | * // This method must be present, even if empty. 31 | * ngOnDestroy() { 32 | * // To protect you, an error will be thrown if it doesn't exist. 33 | * } 34 | * } 35 | * ``` 36 | */ 37 | export function untilDestroyed(instance: object, destroyMethodName: string = 'ngOnDestroy') { 38 | return (source: Observable) => { 39 | const originalDestroy = instance[destroyMethodName]; 40 | const hasDestroyFunction = typeof originalDestroy === 'function'; 41 | 42 | if (!hasDestroyFunction) { 43 | throw new Error( 44 | `${instance.constructor.name} is using untilDestroyed but doesn't implement ${destroyMethodName}` 45 | ); 46 | } 47 | 48 | if (!instance[untilDestroyedSymbol]) { 49 | instance[untilDestroyedSymbol] = new Subject(); 50 | 51 | instance[destroyMethodName] = function () { 52 | if (hasDestroyFunction) { 53 | originalDestroy.apply(this, arguments); 54 | } 55 | instance[untilDestroyedSymbol].next(); 56 | instance[untilDestroyedSymbol].complete(); 57 | }; 58 | } 59 | 60 | return source.pipe(takeUntil(instance[untilDestroyedSymbol])); 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/app/functional/functional.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | 5 | 6 | @NgModule({ 7 | declarations: [], 8 | imports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class FunctionalModule { } 13 | -------------------------------------------------------------------------------- /src/app/shared/header/header.component.css: -------------------------------------------------------------------------------- 1 | .header_block{ 2 | min-height: 55px; 3 | background:#000 linear-gradient(-35deg, #0066a1 0%, #003478 40%, #081026 100%) repeat scroll 0 0;filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#081026', endColorstr='#0066a1', GradientType=1); 4 | background-color: #2b86b2; 5 | } 6 | 7 | .header_block .col-sm-4{ 8 | padding-left: 10px; 9 | padding-right: 10px; 10 | } 11 | 12 | .header_block h2 { 13 | background-repeat: no-repeat; 14 | background-position: 0 0; 15 | background-size: 22% auto; 16 | height: 45%; 17 | width: 65%; 18 | display: inline-block; 19 | margin: 2% 1.5%; 20 | font-size: 18px; 21 | color: #fff; 22 | font-family: centrale-sans; 23 | } 24 | 25 | .header_block .pageMenu { 26 | list-style: none; 27 | margin: 0; 28 | padding: 0; 29 | } 30 | 31 | .header_block .pageMenu li { 32 | display: inline; 33 | line-height: 55px; 34 | font-size: 16px; 35 | font-weight: 600; 36 | } 37 | 38 | .header_block .pageMenu li a { 39 | color: #fff; 40 | text-decoration: none; 41 | padding: 12px 20px; 42 | outline: 0; 43 | } 44 | 45 | .header_block .pageMenu li a.active { 46 | border-bottom: 5px solid #fff; 47 | } 48 | 49 | .header_block .pageMenu li a:hover { 50 | border-bottom: 5px solid #1569c7; 51 | color: #fff; 52 | } 53 | 54 | .header_block .navbar { 55 | margin-bottom: 0px; 56 | } 57 | 58 | .header_block .navbar .top-nav { 59 | list-style: none; 60 | margin: 5px 8px; 61 | padding: 0; 62 | position: relative; 63 | white-space: nowrap; 64 | line-height: 43px; 65 | float: right; 66 | width: auto; 67 | } 68 | 69 | .header_block .navbar .top-nav a { 70 | text-decoration: none; 71 | color: #fff; 72 | display: inline-block; 73 | margin-left: 20px; 74 | } -------------------------------------------------------------------------------- /src/app/shared/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{appName}}

5 |
6 |
7 |
8 |
-------------------------------------------------------------------------------- /src/app/shared/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | import { CommonService } from '../services/common.service'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | class MockCommonService { 8 | getUserDetails() { 9 | return Promise.resolve(true); 10 | } 11 | } 12 | describe('HeaderComponent', () => { 13 | let component: HeaderComponent; 14 | let fixture: ComponentFixture; 15 | 16 | beforeEach(async(() => { 17 | TestBed.configureTestingModule({ 18 | imports: [RouterTestingModule], 19 | declarations: [HeaderComponent], 20 | providers: [ 21 | { provide: CommonService, useClass: MockCommonService } 22 | ] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(HeaderComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | it('should trigger router events', () => { 37 | expect(component).toBeTruthy(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/app/shared/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header', 5 | templateUrl: './header.component.html', 6 | styleUrls: ['./header.component.css'] 7 | }) 8 | export class HeaderComponent { 9 | 10 | public appName = 'angular-basics'; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/shared/landing/landing.component.css: -------------------------------------------------------------------------------- 1 | .main_conatiner{ 2 | width: 100%; 3 | margin: 0 auto; 4 | padding: 0; 5 | height: 100%; 6 | } 7 | 8 | .dashboard_container { 9 | height: 88%; 10 | } 11 | 12 | .dashboard_left{ 13 | width: 300px; 14 | float: left; 15 | /* height: 1000px; */ 16 | height: 100%; 17 | transition: width 200ms; 18 | } 19 | 20 | .dashboard_right{ 21 | width: calc(100% - 50px); 22 | transition: width 200ms; 23 | height: calc(100% - 50px); 24 | } 25 | 26 | .dashboard_left.active{ 27 | width: 50px; 28 | } 29 | 30 | .dashboard_right.active{ 31 | width: calc(100% - 52px); 32 | } 33 | 34 | .dashboard_grid{ 35 | padding: 0px; 36 | } 37 | 38 | .dashboard_detail{ 39 | padding-left: 5px; 40 | padding-right: 10px; 41 | } 42 | -------------------------------------------------------------------------------- /src/app/shared/landing/landing.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 | 8 |
9 |
10 |
11 | Test dashboard 12 | 13 |
14 |
15 |
16 |
17 |
18 | 19 | -------------------------------------------------------------------------------- /src/app/shared/landing/landing.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { of } from 'rxjs'; 6 | import { HeaderComponent } from '../header/header.component'; 7 | import { LeftpaneComponent } from '../leftpane/leftpane.component'; 8 | import { LandingComponent } from './landing.component'; 9 | import { CommonService } from '../services/common.service'; 10 | import { Logger } from '../../core/logger.service'; 11 | import { HttpRequestService } from '../../core/http/http-request.service'; 12 | import { MockComponent } from 'ng-mocks'; 13 | 14 | class MockCommonService { 15 | getUserDetails() { 16 | return of(true); 17 | } 18 | } 19 | class MockLoggerService { 20 | log () { 21 | return of(true); 22 | } 23 | } 24 | 25 | describe('LandingComponent', () => { 26 | let component: LandingComponent; 27 | let fixture: ComponentFixture; 28 | 29 | beforeEach(async(() => { 30 | TestBed.configureTestingModule({ 31 | imports: [ RouterTestingModule ], 32 | declarations: [ MockComponent(HeaderComponent), MockComponent(LeftpaneComponent), LandingComponent ], 33 | providers: [ {provide: CommonService, userClass: MockCommonService}, 34 | {provide: Logger, userClass: MockLoggerService}, HttpRequestService], 35 | schemas: [ NO_ERRORS_SCHEMA ] 36 | }) 37 | .compileComponents(); 38 | })); 39 | 40 | beforeEach(() => { 41 | fixture = TestBed.createComponent(LandingComponent); 42 | component = fixture.componentInstance; 43 | fixture.detectChanges(); 44 | }); 45 | 46 | it('should create', () => { 47 | expect(component).toBeTruthy(); 48 | }); 49 | it('should call sidebarToggleClass()', () => { 50 | component.sidebarToggleClass(); 51 | expect(component.sidebarToggleClass).toBeTruthy(); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/app/shared/landing/landing.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | /** 4 | * 5 | * 6 | * @export 7 | * @class LandingComponent 8 | */ 9 | @Component({ 10 | selector: 'app-landing', 11 | templateUrl: './landing.component.html', 12 | styleUrls: ['./landing.component.css'] 13 | }) 14 | export class LandingComponent { 15 | 16 | public isSidebarActive = false; 17 | public isContainerActive = false; 18 | 19 | /** 20 | * @function sidebarToggleClass 21 | * @description Sidebar ToggleClass function 22 | * */ 23 | public sidebarToggleClass() { 24 | this.isSidebarActive = !this.isSidebarActive; 25 | this.isContainerActive = !this.isContainerActive; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/shared/leftpane/leftpane.component.css: -------------------------------------------------------------------------------- 1 | .sidebar_block{ 2 | height: calc(100% - 50px); 3 | overflow: auto; 4 | border-right: black 1px; 5 | } 6 | .sidebar_block h3{ 7 | border-bottom: 1px solid #dcdcdc; 8 | padding: 10px; 9 | margin: 0px 0px 10px; 10 | font-size: 16px; 11 | text-transform: uppercase; 12 | } 13 | 14 | .toggle-icon{ 15 | color: #212121; 16 | font-size: 1.75em; 17 | padding-left: 20px; 18 | cursor: pointer; 19 | } 20 | 21 | .toggle-icon:before{ 22 | content: ' \2039'; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/shared/leftpane/leftpane.component.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | 9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /src/app/shared/leftpane/leftpane.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LeftpaneComponent } from './leftpane.component'; 4 | 5 | describe('LeftpaneComponent', () => { 6 | let component: LeftpaneComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ ], 12 | declarations: [ 13 | LeftpaneComponent ], 14 | providers: [] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(LeftpaneComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | it('should callToggle()', () => { 29 | component.callToggle(); 30 | expect(component.callToggle).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/shared/leftpane/leftpane.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-leftpane', 5 | templateUrl: './leftpane.component.html', 6 | styleUrls: ['./leftpane.component.css'] 7 | }) 8 | export class LeftpaneComponent { 9 | 10 | public isSidebarActive: boolean; 11 | 12 | @Output() 13 | public changeToggleEvent = new EventEmitter(); 14 | 15 | /** 16 | * @function callToggle 17 | * @description emit left pane expand/collapse 18 | */ 19 | public callToggle() { 20 | this.isSidebarActive = !this.isSidebarActive; 21 | this.changeToggleEvent.emit(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/shared/loader/loader.component.css: -------------------------------------------------------------------------------- 1 | .fa { 2 | vertical-align: middle; 3 | } 4 | .loader-overlay { 5 | position: absolute; 6 | top: 0; 7 | width: 100%; 8 | height: 100%; 9 | opacity: 0.6; 10 | z-index: 1; 11 | background-color: #000; 12 | } 13 | 14 | .loader-overlay .loader { 15 | position: absolute; 16 | top: 50%; 17 | left: 50%; 18 | } -------------------------------------------------------------------------------- /src/app/shared/loader/loader.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 |
7 | 8 | 11 | -------------------------------------------------------------------------------- /src/app/shared/loader/loader.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SharedModule } from '../shared.module'; 4 | import { AppLoaderComponent } from './loader.component'; 5 | import { LoaderService } from './service/loader.service'; 6 | 7 | describe('LoaderComponent', () => { 8 | let component: AppLoaderComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ SharedModule ], 14 | declarations: [], 15 | providers: [ LoaderService ] 16 | }).compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(AppLoaderComponent); 21 | component = fixture.componentInstance; 22 | new LoaderService().showBlockLoader(); 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should not be visible by default', () => { 27 | // Arrange 28 | const element = fixture.nativeElement; 29 | const div = element.querySelectorAll('dls-layout-container')[0]; 30 | 31 | // Assert 32 | expect(div).toBeUndefined(); 33 | // expect(div.getAttribute('*ngIf')).not.toBeNull(); 34 | }); 35 | /* it('should be visible', () => { 36 | // Arrange 37 | const element = fixture.nativeElement; 38 | const div = element.querySelectorAll('dls-layout-container')[0]; 39 | 40 | // Assert 41 | expect(div).toBeDefined(); 42 | //expect(div.getAttribute('*ngIf')).not.toBeNull(); 43 | });*/ 44 | 45 | /* it('should be visible when app is loading', () => { 46 | // Arrange 47 | const element = fixture.nativeElement; 48 | const div = element.querySelectorAll('div')[0]; 49 | 50 | // Act 51 | new LoaderService().show(); 52 | fixture.componentInstance.isLoading = new LoaderService().isAppLoading; 53 | console.log('loader: ', fixture.componentInstance.isLoading); 54 | fixture.detectChanges(); 55 | 56 | // Assert 57 | expect(div.getAttribute('hidden')).toBeNull(); 58 | }); 59 | 60 | it('should not display a message by default', () => { 61 | // Arrange 62 | const element = fixture.nativeElement; 63 | const span = element.querySelectorAll('span')[0]; 64 | 65 | // Assert 66 | expect(span.textContent).toBe(''); 67 | }); 68 | 69 | it('should display specified message', () => { 70 | // Arrange 71 | const element = fixture.nativeElement; 72 | const span = element.querySelectorAll('span')[0]; 73 | 74 | // Act 75 | fixture.componentInstance.message = 'testing'; 76 | fixture.detectChanges(); 77 | 78 | // Assert 79 | expect(span.textContent).toBe('testing'); 80 | });*/ 81 | }); 82 | -------------------------------------------------------------------------------- /src/app/shared/loader/loader.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { LoaderService } from './service/loader.service'; 3 | import { Subject } from 'rxjs'; 4 | 5 | /** 6 | * 7 | * 8 | * @export 9 | * @class AppLoaderComponent 10 | */ 11 | @Component({ 12 | selector: 'app-loader', 13 | templateUrl: './loader.component.html', 14 | styleUrls: ['./loader.component.css'], 15 | }) 16 | export class AppLoaderComponent { 17 | @Input() 18 | public message: string | undefined; 19 | 20 | public isLoading: Subject = this.loaderService.isAppLoading; 21 | 22 | /** 23 | *Creates an instance of AppLoaderComponent. 24 | * @param {LoaderService} loaderService 25 | * @memberof AppLoaderComponent 26 | */ 27 | public constructor(private loaderService: LoaderService) { } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/shared/loader/service/loader.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 3 | import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; 4 | 5 | import { LoaderInterceptor } from './loader.interceptor'; 6 | import { LoaderService } from './loader.service'; 7 | import { finalize } from 'rxjs/operators'; 8 | 9 | describe('LoaderInterceptor', () => { 10 | let loaderInterceptor: LoaderInterceptor; 11 | let http: HttpClient; 12 | let loaderService: LoaderService; 13 | 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | providers: [LoaderInterceptor, 17 | LoaderService, 18 | { 19 | provide: HTTP_INTERCEPTORS, 20 | useClass: LoaderInterceptor, 21 | multi: true, 22 | } 23 | ], 24 | imports: [HttpClientTestingModule] 25 | }); 26 | http = TestBed.get(HttpClient); 27 | loaderService = TestBed.get(LoaderService); 28 | loaderInterceptor = TestBed.get(LoaderInterceptor); 29 | spyOn(loaderService, 'showBlockLoader'); 30 | spyOn(loaderService, 'hideBlockLoader'); 31 | }); 32 | it('When request starts the loader starts and ends as expected', async(() => { 33 | const url = 'dashboard/usercomponent'; 34 | getLoadervalidator(http, url, loaderService); 35 | 36 | })); 37 | 38 | it('When request starts the loader starts and ends as expected', async(() => { 39 | const url = 'dashboard/renameWorkflow'; 40 | // Make an HTTP GET request 41 | getLoadervalidator(http, url, loaderService); 42 | 43 | })); 44 | 45 | it('invoke hideLoader()', async(() => { 46 | loaderInterceptor.hideLoader(); 47 | expect(loaderService.showBlockLoader).toBeTruthy(); 48 | })); 49 | }); 50 | /** 51 | * 52 | * 53 | * @param {HttpClient} http 54 | * @param {string} url 55 | * @param {LoaderService} loaderService 56 | */ 57 | function getLoadervalidator(http: HttpClient, url: string, loaderService: LoaderService) { 58 | http.get(url) 59 | .pipe( 60 | finalize(() => expect(loaderService.hideBlockLoader).toHaveBeenCalledTimes(1)) 61 | ).subscribe((res) => { 62 | expect(loaderService.showBlockLoader).toHaveBeenCalledTimes(1); 63 | }); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/app/shared/loader/service/loader.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { finalize } from 'rxjs/operators'; 5 | import { LoaderService } from './loader.service'; 6 | 7 | /** 8 | * 9 | * 10 | * @export 11 | * @class LoaderInterceptor 12 | * @implements {HttpInterceptor} 13 | */ 14 | @Injectable() 15 | export class LoaderInterceptor implements HttpInterceptor { 16 | private activeRequests = 0; 17 | 18 | public constructor(public loaderService: LoaderService) { } 19 | 20 | public intercept(req: HttpRequest, next: HttpHandler): Observable> { 21 | // first request 22 | if (this.activeRequests === 0) { 23 | this.showLoader(); 24 | } 25 | // increment each request 26 | this.activeRequests++; 27 | 28 | return next.handle(req).pipe( 29 | finalize(() => { 30 | // decrement each request 31 | this.activeRequests--; 32 | // stop when last request 33 | if (this.activeRequests === 0) { 34 | this.hideLoader(); 35 | } 36 | }) 37 | ); 38 | } 39 | 40 | /** 41 | * @function showLoader 42 | * @description trigger show loader 43 | */ 44 | public showLoader () { 45 | this.loaderService.showBlockLoader(); 46 | } 47 | 48 | /** 49 | * @function hideLoader 50 | * @description trigger hide loader 51 | */ 52 | public hideLoader () { 53 | this.loaderService.hideBlockLoader(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/shared/loader/service/loader.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { LoaderService } from './loader.service'; 4 | 5 | describe('LoaderService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({ 7 | providers: [ LoaderService ] 8 | })); 9 | 10 | it('should be created', () => { 11 | const service: LoaderService = TestBed.get(LoaderService); 12 | expect(service).toBeTruthy(); 13 | }); 14 | it('emit - block loader hide', () => { 15 | const service: LoaderService = TestBed.get(LoaderService); 16 | service.hideBlockLoader(); 17 | expect(service.hideBlockLoader).toBeTruthy(); 18 | }); 19 | it('emit - block loader show', () => { 20 | const service: LoaderService = TestBed.get(LoaderService); 21 | service.showBlockLoader(); 22 | expect(service.showBlockLoader).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/shared/loader/service/loader.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | /** 5 | * 6 | * 7 | * @export 8 | * @class LoaderService 9 | */ 10 | @Injectable() 11 | export class LoaderService { 12 | 13 | // public isCompLoading = new Subject(); 14 | public isAppLoading = new Subject(); 15 | 16 | /** 17 | * @function showBlockLoader 18 | * @description emit to show app loader 19 | */ 20 | public showBlockLoader() { 21 | this.isAppLoading.next(true); 22 | } 23 | /** 24 | * @function hideBlockLoader 25 | * @description emit to hide app loader 26 | */ 27 | public hideBlockLoader() { 28 | this.isAppLoading.next(false); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/shared/services/commom-service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { Type } from '@angular/core'; 5 | import { CommonService } from './common.service'; 6 | import { HttpRequestService } from '../../core/http/http-request.service'; 7 | 8 | describe('common-service', () => { 9 | let http: HttpClient; 10 | let httpMock: HttpTestingController; 11 | // let responseData: any; 12 | const mockResponseData = 'abhishek.ankush'; 13 | 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [HttpClientTestingModule], 17 | providers: [HttpClient, HttpRequestService] 18 | }); 19 | http = TestBed.get(HttpClient); 20 | httpMock = TestBed.get(HttpTestingController as Type); 21 | }); 22 | 23 | afterEach(() => { 24 | httpMock.verify(); 25 | }); 26 | 27 | it('should create an instance', () => { 28 | expect(new CommonService(TestBed.get(HttpRequestService))).toBeTruthy(); 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/shared/services/common.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequestService } from '../../core/http/http-request.service'; 3 | import { Subject } from 'rxjs'; 4 | 5 | @Injectable() 6 | export class CommonService { 7 | 8 | public constructor(private _http: HttpRequestService) { } 9 | 10 | public invokeEvent: Subject = new Subject(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule as AngularCommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { HTTP_INTERCEPTORS } from '@angular/common/http'; 6 | 7 | import { HeaderComponent } from './header/header.component'; 8 | import { LeftpaneComponent } from './leftpane/leftpane.component'; 9 | import { AppLoaderComponent } from './loader/loader.component'; 10 | import { LandingComponent } from './landing/landing.component'; 11 | 12 | 13 | import { CommonService } from './services/common.service'; 14 | 15 | import { LoaderService } from './loader/service/loader.service'; 16 | import { LoaderInterceptor } from './loader/service/loader.interceptor'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | AngularCommonModule, 21 | RouterModule, 22 | FormsModule 23 | ], 24 | declarations: [ 25 | AppLoaderComponent, 26 | HeaderComponent, 27 | LeftpaneComponent, 28 | LandingComponent 29 | ], 30 | providers: [ 31 | CommonService, 32 | LoaderService, 33 | { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true } 34 | ], 35 | exports: [ 36 | AppLoaderComponent, 37 | HeaderComponent, 38 | LeftpaneComponent, 39 | LandingComponent 40 | ], 41 | }) 42 | export class SharedModule {} 43 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abhishek-Ankush-git/angular-basic/a7e868561fdcba4b325b47f57371720d4a5b1523/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | serverUrl: '' 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | serverUrl: '' 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abhishek-Ankush-git/angular-basic/a7e868561fdcba4b325b47f57371720d4a5b1523/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularBasics 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /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().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /stryker.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json", 3 | "comment": "This config was generated using a preset. Please see the handbook for more information: https://github.com/stryker-mutator/stryker-handbook/blob/master/stryker/guides/angular.md#angular", 4 | "mutate": [ 5 | "src/**/*.ts", 6 | "!src/**/*.spec.ts", 7 | "!src/test.ts", 8 | "!src/environments/*.ts" 9 | ], 10 | "mutator": "typescript", 11 | "testRunner": "karma", 12 | "karma": { 13 | "configFile": "src/karma.conf.js", 14 | "projectType": "angular-cli", 15 | "config": { 16 | "browsers": [ 17 | "ChromeHeadless" 18 | ], 19 | "customLaunchers": { 20 | "ChromeHeadless": { 21 | "base": "Chrome", 22 | "flags": [ 23 | "--headless", 24 | "--disable-gpu", 25 | "--no-sandbox", 26 | "--remote-debugging-port=9222" 27 | ] 28 | } 29 | } 30 | } 31 | }, 32 | 33 | "reporters": [ 34 | "progress", 35 | "clear-text", 36 | "html" 37 | ], 38 | "maxConcurrentTestRunners": 4, 39 | "maxConcurrentTestRunners_comment": "Recommended to use about half of your available cores when running stryker with angular", 40 | "coverageAnalysis": "off" 41 | 42 | } -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "app", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "app", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef": [ 98 | true, 99 | "call-signature" 100 | ], 101 | "typedef-whitespace": { 102 | "options": [ 103 | { 104 | "call-signature": "nospace", 105 | "index-signature": "nospace", 106 | "parameter": "nospace", 107 | "property-declaration": "nospace", 108 | "variable-declaration": "nospace" 109 | }, 110 | { 111 | "call-signature": "onespace", 112 | "index-signature": "onespace", 113 | "parameter": "onespace", 114 | "property-declaration": "onespace", 115 | "variable-declaration": "onespace" 116 | } 117 | ] 118 | }, 119 | "variable-name": { 120 | "options": [ 121 | "ban-keywords", 122 | "check-format", 123 | "allow-pascal-case" 124 | ] 125 | }, 126 | "whitespace": { 127 | "options": [ 128 | "check-branch", 129 | "check-decl", 130 | "check-operator", 131 | "check-separator", 132 | "check-type", 133 | "check-typecast" 134 | ] 135 | }, 136 | "no-conflicting-lifecycle": true, 137 | "no-host-metadata-property": true, 138 | "no-input-rename": true, 139 | "no-inputs-metadata-property": true, 140 | "no-output-native": true, 141 | "no-output-on-prefix": true, 142 | "no-output-rename": true, 143 | "no-outputs-metadata-property": true, 144 | "template-banana-in-box": true, 145 | "template-no-negated-async": true, 146 | "use-lifecycle-interface": true, 147 | "use-pipe-transform-interface": true 148 | }, 149 | "rulesDirectory": [ 150 | "codelyzer" 151 | ] 152 | } --------------------------------------------------------------------------------