├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── angular.json ├── e2e ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── server.ts ├── server.ts.bak ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.ts │ ├── app.module.ts │ ├── app.server.module.ts │ ├── dashboard │ │ ├── dashboard.component.css │ │ ├── dashboard.component.html │ │ ├── dashboard.component.spec.ts │ │ └── dashboard.component.ts │ ├── hero-detail │ │ ├── hero-detail.component.css │ │ ├── hero-detail.component.html │ │ └── hero-detail.component.ts │ ├── hero-search │ │ ├── hero-search.component.css │ │ ├── hero-search.component.html │ │ ├── hero-search.component.spec.ts │ │ └── hero-search.component.ts │ ├── hero.service.ts │ ├── hero.ts │ ├── heroes │ │ ├── heroes.component.css │ │ ├── heroes.component.html │ │ ├── heroes.component.spec.ts │ │ ├── heroes.component.ts │ │ ├── heroes.module-routing.ts │ │ └── heroes.module.ts │ ├── heroes2 │ │ ├── heroes.component.css │ │ ├── heroes.component.html │ │ ├── heroes.component.spec.ts │ │ ├── heroes.component.ts │ │ └── heroes.module-routing.ts │ ├── in-memory-data.service.ts │ ├── message.service.spec.ts │ ├── message.service.ts │ ├── messages │ │ ├── messages.component.css │ │ ├── messages.component.html │ │ ├── messages.component.spec.ts │ │ └── messages.component.ts │ └── mock-heroes.ts ├── assets │ └── locales │ │ ├── en.json │ │ └── fr.json ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.server.ts ├── main.ts ├── styles.css ├── test.ts └── typings.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.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 | -------------------------------------------------------------------------------- /.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 | /deploy 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.angular/cache 30 | /.sass-cache 31 | /connect.lock 32 | /coverage 33 | /libpeerconnection.log 34 | npm-debug.log 35 | testem.log 36 | /typings 37 | package-lock.json 38 | 39 | # e2e 40 | /e2e/*.js 41 | /e2e/*.map 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": [ 3 | "src/assets/locales", 4 | "src/app/messages" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-universal-localize-router 2 | 3 | Angular (5-17) + universal + ngx-translate + ngx-translate-router + lazyloading 4 | 5 | ## Find appropriate source 6 | 7 | Choose the branch when you clone this repo: 8 | * angular-5 9 | * angular-7 10 | * angular-8 11 | * angular-9 12 | * angular-10 13 | * angular-11 14 | * angular-12 15 | * angular-13 16 | * angular-14 17 | * angular-16 18 | * angular-17 19 | 20 | ## Development server 21 | 22 | Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 23 | 24 | # Start universal dev server 25 | 26 | Run `npm run dev:ssr` to start the project with universal in debug mode. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 27 | 28 | ## Build 29 | 30 | Run `npm run build:ssr` to build the project. The build artifacts will be stored in the `dist/` directory. 31 | 32 | ## Universal server 33 | 34 | Run `npm run serve:ssr` for a universal server. Navigate to `http://localhost:4000/`. 35 | 36 | ## Run artifacts on server 37 | 38 | Copy `browser` and `server` folders in the same server on your server. 39 | 40 | Go to the parent folder and run `node server/main.js`. 41 | 42 | Navigate to `http://localhost:4000/`. 43 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular.io-example": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:application", 14 | "options": { 15 | "outputPath": "dist/example", 16 | "index": "src/index.html", 17 | "browser": "src/main.ts", 18 | "tsConfig": "tsconfig.app.json", 19 | "polyfills": [ 20 | "zone.js" 21 | ], 22 | "assets": [ 23 | "src/assets", 24 | "src/favicon.ico" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [], 30 | "preserveSymlinks": true, 31 | "extractLicenses": false, 32 | "sourceMap": true, 33 | "optimization": false, 34 | "namedChunks": true, 35 | "server": "src/main.server.ts", 36 | "prerender": false, 37 | "ssr": { 38 | "entry": "server.ts" 39 | } 40 | }, 41 | "configurations": { 42 | "production": { 43 | "budgets": [ 44 | { 45 | "type": "initial", 46 | "maximumWarning": "1500kb", 47 | "maximumError": "2mb" 48 | }, 49 | { 50 | "type": "anyComponentStyle", 51 | "maximumWarning": "4kb", 52 | "maximumError": "10kb" 53 | } 54 | ], 55 | "outputHashing": "all" 56 | }, 57 | "development": { 58 | "optimization": false, 59 | "extractLicenses": false, 60 | "sourceMap": true 61 | } 62 | }, 63 | "defaultConfiguration": "production" 64 | }, 65 | "serve": { 66 | "builder": "@angular-devkit/build-angular:dev-server", 67 | "configurations": { 68 | "production": { 69 | "buildTarget": "angular.io-example:build:production" 70 | }, 71 | "development": { 72 | "buildTarget": "angular.io-example:build:development" 73 | } 74 | }, 75 | "defaultConfiguration": "development" 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "buildTarget": "angular.io-example:build" 81 | } 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "main": "src/test.ts", 87 | "karmaConfig": "./karma.conf.js", 88 | "polyfills": "src/polyfills.ts", 89 | "tsConfig": "tsconfig.spec.json", 90 | "scripts": [], 91 | "styles": [ 92 | "src/styles.css" 93 | ], 94 | "assets": [ 95 | "src/assets", 96 | "src/favicon.ico" 97 | ] 98 | } 99 | } 100 | } 101 | }, 102 | "angular.io-example-e2e": { 103 | "root": "e2e", 104 | "sourceRoot": "e2e", 105 | "projectType": "application", 106 | "architect": { 107 | "e2e": { 108 | "builder": "@angular-devkit/build-angular:protractor", 109 | "options": { 110 | "protractorConfig": "./protractor.conf.js", 111 | "devServerTarget": "angular.io-example:serve" 112 | } 113 | } 114 | } 115 | } 116 | }, 117 | "cli": { 118 | "analytics": "9a87127c-3971-4fb7-a06a-80d2cf11e87c" 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /e2e/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/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 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'), reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-io-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Example project from an angular.io guide.", 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "watch": "ng build --watch --configuration development", 11 | "test": "ng test", 12 | "serve:ssr": "node dist/example/server/server.mjs" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@angular/animations": "^17.0.4", 19 | "@angular/common": "^17.0.4", 20 | "@angular/compiler": "^17.0.4", 21 | "@angular/core": "^17.0.4", 22 | "@angular/forms": "^17.0.4", 23 | "@angular/platform-browser": "^17.0.4", 24 | "@angular/platform-browser-dynamic": "^17.0.4", 25 | "@angular/platform-server": "^17.0.4", 26 | "@angular/router": "^17.0.4", 27 | "@angular/ssr": "^17.0.3", 28 | "@gilsdav/ngx-translate-router": "7.0.0", 29 | "@gilsdav/ngx-translate-router-http-loader": "~2.0.0", 30 | "@ngx-translate/core": "~15.0.0", 31 | "@ngx-translate/http-loader": "~8.0.0", 32 | "angular-in-memory-web-api": "~0.17.0", 33 | "core-js": "~2.5.7", 34 | "express": "^4.15.2", 35 | "rxjs": "~7.4.0", 36 | "ts-loader": "~5.3.0", 37 | "tslib": "^2.0.0", 38 | "zone.js": "~0.14.2" 39 | }, 40 | "devDependencies": { 41 | "@angular-devkit/build-angular": "^17.0.3", 42 | "@angular/cli": "^17.0.3", 43 | "@angular/compiler-cli": "^17.0.4", 44 | "@angular/language-service": "^17.0.4", 45 | "@types/express": "^4.17.0", 46 | "@types/jasmine": "~5.1.0", 47 | "@types/node": "^18.18.0", 48 | "codelyzer": "^6.0.0", 49 | "jasmine-core": "~5.1.0", 50 | "karma": "~6.4.0", 51 | "karma-chrome-launcher": "~3.2.0", 52 | "karma-coverage": "~2.2.0", 53 | "karma-jasmine": "~5.1.0", 54 | "karma-jasmine-html-reporter": "~2.1.0", 55 | "jasmine-spec-reporter": "~5.0.0", 56 | "karma-coverage-istanbul-reporter": "~3.0.2", 57 | "karma-phantomjs-launcher": "~1.0.2", 58 | "lodash": "^4.16.2", 59 | "phantomjs-prebuilt": "^2.1.7", 60 | "protractor": "~7.0.0", 61 | "ts-loader": "^5.2.0", 62 | "ts-node": "~7.0.1", 63 | "tslint": "~6.1.0", 64 | "typescript": "~5.2.2" 65 | }, 66 | "repository": {} 67 | } 68 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome', 13 | // For Travis CI only 14 | chromeOptions: { 15 | binary: process.env.CHROME_BIN, 16 | args: ['--no-sandbox'] 17 | } 18 | }, 19 | directConnect: true, 20 | baseUrl: 'http://localhost:4200/', 21 | framework: 'jasmine', 22 | jasmineNodeOpts: { 23 | showColors: true, 24 | defaultTimeoutInterval: 30000, 25 | print: function() {} 26 | }, 27 | onPrepare() { 28 | require('ts-node').register({ 29 | project: 'e2e/tsconfig.e2e.json' 30 | }); 31 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | import { APP_BASE_HREF } from '@angular/common'; 2 | import { CommonEngine } from '@angular/ssr'; 3 | import express from 'express'; 4 | import { fileURLToPath } from 'node:url'; 5 | import { dirname, join, resolve } from 'node:path'; 6 | import AppServerModule from './src/main.server'; 7 | 8 | // The Express app is exported so that it can be used by serverless Functions. 9 | export function app(): express.Express { 10 | const server = express(); 11 | const serverDistFolder = dirname(fileURLToPath(import.meta.url)); 12 | const browserDistFolder = resolve(serverDistFolder, '../browser'); 13 | const indexHtml = join(serverDistFolder, 'index.server.html'); 14 | 15 | const commonEngine = new CommonEngine(); 16 | 17 | server.set('view engine', 'html'); 18 | server.set('views', browserDistFolder); 19 | 20 | // Example Express Rest API endpoints 21 | // server.get('/api/**', (req, res) => { }); 22 | // Serve static files from /browser 23 | server.get('*.*', express.static(browserDistFolder, { 24 | maxAge: '1y' 25 | })); 26 | 27 | // All regular routes use the Angular engine 28 | server.get('*', (req, res, next) => { 29 | const { protocol, originalUrl, baseUrl, headers } = req; 30 | 31 | commonEngine 32 | .render({ 33 | bootstrap: AppServerModule, 34 | documentFilePath: indexHtml, 35 | url: `${protocol}://${headers.host}${originalUrl}`, 36 | publicPath: browserDistFolder, 37 | providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], 38 | }) 39 | .then((html) => res.send(html)) 40 | .catch((err) => next(err)); 41 | }); 42 | 43 | return server; 44 | } 45 | 46 | function run(): void { 47 | const port = process.env['PORT'] || 4200; 48 | 49 | // Start up the Node server 50 | const server = app(); 51 | server.listen(port, () => { 52 | console.log(`Node Express server listening on http://localhost:${port}`); 53 | }); 54 | } 55 | 56 | run(); 57 | -------------------------------------------------------------------------------- /server.ts.bak: -------------------------------------------------------------------------------- 1 | import 'zone.js/node'; 2 | 3 | import { ngExpressEngine } from '@nguniversal/express-engine'; 4 | import * as express from 'express'; 5 | import { join } from 'path'; 6 | 7 | import { AppServerModule } from './src/main.server'; 8 | import { APP_BASE_HREF } from '@angular/common'; 9 | import { environment } from './src/environments/environment'; 10 | 11 | // The Express app is exported so that it can be used by serverless Functions. 12 | export function app() { 13 | const server = express(); 14 | const distFolder = join(process.cwd(), environment.browserDirPath); 15 | 16 | // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) 17 | server.engine('html', ngExpressEngine({ 18 | bootstrap: AppServerModule, 19 | })); 20 | 21 | server.set('view engine', 'html'); 22 | server.set('views', distFolder); 23 | 24 | // Example Express Rest API endpoints 25 | // app.get('/api/**', (req, res) => { }); 26 | // Serve static files from /browser 27 | server.get('*.*', express.static(distFolder, { 28 | maxAge: '1y' 29 | })); 30 | 31 | // All regular routes use the Universal engine 32 | server.get('*', (req, res) => { 33 | res.render('index', { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] }); 34 | }); 35 | 36 | return server; 37 | } 38 | 39 | function run() { 40 | const port = process.env.PORT || 4000; 41 | 42 | // Start up the Node server 43 | const server = app(); 44 | server.listen(port, () => { 45 | console.log(`Node Express server listening on http://localhost:${port}`); 46 | }); 47 | } 48 | 49 | // Webpack will replace 'require' with '__webpack_require__' 50 | // '__non_webpack_require__' is a proxy to Node 'require' 51 | // The below code is to ensure that the server is run only when not requiring the bundle. 52 | declare const __non_webpack_require__: NodeRequire; 53 | const mainModule = __non_webpack_require__.main; 54 | if (mainModule && mainModule.filename === __filename) { 55 | run(); 56 | } 57 | 58 | export * from './src/main.server'; 59 | 60 | // export { renderModule } from '@angular/platform-server'; 61 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | import {Location} from '@angular/common'; 4 | 5 | import {DashboardComponent} from './dashboard/dashboard.component'; 6 | // import { HeroesComponent } from './heroes/heroes.component'; 7 | import {HeroDetailComponent} from './hero-detail/hero-detail.component'; 8 | 9 | import {LocalizeParser, LocalizeRouterModule, LocalizeRouterSettings, ManualParserLoader} from '@gilsdav/ngx-translate-router'; 10 | // import { LocalizeRouterHttpLoader } from '@gilsdav/ngx-translate-router-http-loader'; 11 | import {TranslateService} from '@ngx-translate/core'; 12 | 13 | /* export function createTranslateLoader(translate: TranslateService, location: Location, settings: LocalizeRouterSettings, http: HttpClient) { 14 | return new LocalizeRouterHttpLoader(translate, location, settings, http) 15 | } */ 16 | 17 | export function createTranslateLoader(translate: TranslateService, location: Location, settings: LocalizeRouterSettings) { 18 | return new ManualParserLoader(translate, location, settings, ['en', 'fr'], 'ROUTES.'); 19 | } 20 | 21 | const routes: Routes = [ 22 | {path: '', redirectTo: '/dashboard', pathMatch: 'full'}, 23 | {path: 'dashboard', component: DashboardComponent}, 24 | {path: 'detail/:id', component: HeroDetailComponent}, 25 | // { path: 'heroes', loadChildren: './heroes/heroes.module#HeroesModule' } 26 | {path: 'heroes', loadChildren: () => import('./heroes/heroes.module').then(mod => mod.HeroesModule)}, 27 | {path: 'heroesbis', loadChildren: () => import('./heroes2/heroes.module-routing').then(mod => mod.routes)} 28 | ]; 29 | 30 | @NgModule({ 31 | imports: [ 32 | RouterModule.forRoot(routes, { initialNavigation: 'disabled' }), 33 | LocalizeRouterModule.forRoot(routes, { 34 | parser: { 35 | provide: LocalizeParser, 36 | useFactory: (createTranslateLoader), 37 | deps: [TranslateService, Location, LocalizeRouterSettings/*, HttpClient*/] 38 | }, 39 | initialNavigation: true 40 | }) 41 | ], 42 | exports: [RouterModule, LocalizeRouterModule] 43 | }) 44 | export class AppRoutingModule { 45 | } 46 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | /* AppComponent's private CSS styles */ 2 | h1 { 3 | font-size: 1.2em; 4 | color: #999; 5 | margin-bottom: 0; 6 | } 7 | h2 { 8 | font-size: 2em; 9 | margin-top: 0; 10 | padding-top: 0; 11 | } 12 | nav a { 13 | padding: 5px 10px; 14 | text-decoration: none; 15 | margin-top: 10px; 16 | display: inline-block; 17 | background-color: #eee; 18 | border-radius: 4px; 19 | } 20 | nav a:visited, a:link { 21 | color: #607D8B; 22 | } 23 | nav a:hover { 24 | color: #039be5; 25 | background-color: #CFD8DC; 26 | } 27 | nav a.active { 28 | color: #039be5; 29 | } 30 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

{{title}}

2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TranslateService } from '@ngx-translate/core'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent { 10 | title = 'Tour of Heroes'; 11 | 12 | constructor(translate: TranslateService) { 13 | translate.setDefaultLang('en'); 14 | // translate.use('en'); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {APP_ID, Inject, NgModule, PLATFORM_ID} from '@angular/core'; 2 | import {BrowserModule, provideClientHydration} from '@angular/platform-browser'; 3 | import {FormsModule} from '@angular/forms'; 4 | import {HttpClient, provideHttpClient, withFetch} from '@angular/common/http'; 5 | 6 | import {HttpClientInMemoryWebApiModule} from 'angular-in-memory-web-api'; 7 | import {InMemoryDataService} from './in-memory-data.service'; 8 | 9 | import {AppRoutingModule} from './app-routing.module'; 10 | 11 | import {AppComponent} from './app.component'; 12 | import {DashboardComponent} from './dashboard/dashboard.component'; 13 | import {HeroDetailComponent} from './hero-detail/hero-detail.component'; 14 | // import { HeroesComponent } from './heroes/heroes.component'; 15 | import {HeroSearchComponent} from './hero-search/hero-search.component'; 16 | import {HeroService} from './hero.service'; 17 | import {MessageService} from './message.service'; 18 | import {MessagesComponent} from './messages/messages.component'; 19 | import {isPlatformBrowser} from '@angular/common'; 20 | 21 | import {TranslateHttpLoader} from '@ngx-translate/http-loader'; 22 | import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; 23 | import {environment} from '../environments/environment'; 24 | 25 | // import { UniversalTranslateLoader } from '@ngx-universal/translate-loader'; 26 | 27 | export function createTranslateLoader(http: HttpClient) { 28 | return new TranslateHttpLoader(http, `${environment.locales}assets/locales/`, '.json'); 29 | } 30 | 31 | /*export function createTranslateLoader(platformId: any, http: HttpClient): TranslateLoader { 32 | const browserLoader = new TranslateHttpLoader(http, './assets/locales/', '.json'); 33 | 34 | return new UniversalTranslateLoader(platformId, browserLoader, 'browser/assets/locales', '.json'); 35 | }*/ 36 | 37 | @NgModule({ 38 | imports: [ 39 | BrowserModule, 40 | FormsModule, 41 | AppRoutingModule, 42 | HttpClientInMemoryWebApiModule.forRoot( 43 | InMemoryDataService, {dataEncapsulation: false, passThruUnknownUrl: true} 44 | ), 45 | TranslateModule.forRoot({ 46 | loader: { 47 | provide: TranslateLoader, 48 | useFactory: (createTranslateLoader), 49 | deps: [/* PLATFORM_ID, */HttpClient] 50 | } 51 | }) 52 | ], 53 | declarations: [ 54 | AppComponent, 55 | DashboardComponent, 56 | // HeroesComponent, 57 | HeroDetailComponent, 58 | MessagesComponent, 59 | HeroSearchComponent 60 | ], 61 | providers: [HeroService, MessageService, provideClientHydration(), provideHttpClient(withFetch())], 62 | bootstrap: [AppComponent] 63 | }) 64 | export class AppModule { 65 | constructor( 66 | // tslint:disable-next-line:ban-types 67 | @Inject(PLATFORM_ID) private platformId: Object, 68 | @Inject(APP_ID) private appId: string) { 69 | const platform = isPlatformBrowser(platformId) ? 70 | 'in the browser' : 'on the server'; 71 | console.log(`Running ${platform} with appId=${appId}`); 72 | console.log('locales', environment.locales); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/app/app.server.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ServerModule } from '@angular/platform-server'; 3 | 4 | import { AppModule } from './app.module'; 5 | import { AppComponent } from './app.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | AppModule, 10 | ServerModule 11 | ], 12 | providers: [], 13 | bootstrap: [ AppComponent ], 14 | }) 15 | export class AppServerModule {} 16 | 17 | -------------------------------------------------------------------------------- /src/app/dashboard/dashboard.component.css: -------------------------------------------------------------------------------- 1 | /* DashboardComponent's private CSS styles */ 2 | [class*='col-'] { 3 | float: left; 4 | padding-right: 20px; 5 | padding-bottom: 20px; 6 | } 7 | [class*='col-']:last-of-type { 8 | padding-right: 0; 9 | } 10 | a { 11 | text-decoration: none; 12 | } 13 | *, *:after, *:before { 14 | -webkit-box-sizing: border-box; 15 | -moz-box-sizing: border-box; 16 | box-sizing: border-box; 17 | } 18 | h3 { 19 | text-align: center; margin-bottom: 0; 20 | } 21 | h4 { 22 | position: relative; 23 | } 24 | .grid { 25 | margin: 0; 26 | } 27 | .col-1-4 { 28 | width: 25%; 29 | } 30 | .module { 31 | padding: 20px; 32 | text-align: center; 33 | color: #eee; 34 | max-height: 120px; 35 | min-width: 120px; 36 | background-color: #607D8B; 37 | border-radius: 2px; 38 | } 39 | .module:hover { 40 | background-color: #EEE; 41 | cursor: pointer; 42 | color: #607d8b; 43 | } 44 | .grid-pad { 45 | padding: 10px 0; 46 | } 47 | .grid-pad > [class*='col-']:last-of-type { 48 | padding-right: 20px; 49 | } 50 | @media (max-width: 600px) { 51 | .module { 52 | font-size: 10px; 53 | max-height: 75px; } 54 | } 55 | @media (max-width: 1024px) { 56 | .grid { 57 | margin: 0; 58 | } 59 | .module { 60 | min-width: 60px; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 |

Top Heroes {{ 'HELLO' | translate }}

2 | 3 | 4 | 5 | 6 |
7 | 9 |
10 |

{{hero.name}}

11 |
12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/dashboard/dashboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { DashboardComponent } from './dashboard.component'; 4 | 5 | describe('DashboardComponent', () => { 6 | let component: DashboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DashboardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DashboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dashboard/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import {LocalizeRouterService} from '@gilsdav/ngx-translate-router'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { Hero } from '../hero'; 4 | import { HeroService } from '../hero.service'; 5 | 6 | @Component({ 7 | selector: 'app-dashboard', 8 | templateUrl: './dashboard.component.html', 9 | styleUrls: [ './dashboard.component.css' ] 10 | }) 11 | export class DashboardComponent implements OnInit { 12 | heroes: Hero[] = []; 13 | 14 | constructor(private heroService: HeroService, private localizeService: LocalizeRouterService) { } 15 | 16 | ngOnInit() { 17 | this.getHeroes(); 18 | } 19 | 20 | getHeroes(): void { 21 | this.heroService.getHeroes() 22 | .subscribe(heroes => this.heroes = heroes.slice(1, 5)); 23 | } 24 | 25 | switchLanguage(lang: string) { 26 | this.localizeService.changeLanguage(lang); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/hero-detail/hero-detail.component.css: -------------------------------------------------------------------------------- 1 | /* HeroDetailComponent's private CSS styles */ 2 | label { 3 | display: inline-block; 4 | width: 3em; 5 | margin: .5em 0; 6 | color: #607D8B; 7 | font-weight: bold; 8 | } 9 | input { 10 | height: 2em; 11 | font-size: 1em; 12 | padding-left: .4em; 13 | } 14 | button { 15 | margin-top: 20px; 16 | font-family: Arial; 17 | background-color: #eee; 18 | border: none; 19 | padding: 5px 10px; 20 | border-radius: 4px; 21 | cursor: pointer; cursor: hand; 22 | } 23 | button:hover { 24 | background-color: #cfd8dc; 25 | } 26 | button:disabled { 27 | background-color: #eee; 28 | color: #ccc; 29 | cursor: auto; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/hero-detail/hero-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ hero.name | uppercase }} Details

3 |
id: {{hero.id}}
4 |
5 | 8 |
9 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/hero-detail/hero-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Location } from '@angular/common'; 4 | 5 | import { Hero } from '../hero'; 6 | import { HeroService } from '../hero.service'; 7 | 8 | @Component({ 9 | selector: 'app-hero-detail', 10 | templateUrl: './hero-detail.component.html', 11 | styleUrls: [ './hero-detail.component.css' ] 12 | }) 13 | export class HeroDetailComponent implements OnInit { 14 | @Input() hero!: Hero; 15 | 16 | constructor( 17 | private route: ActivatedRoute, 18 | private heroService: HeroService, 19 | private location: Location 20 | ) {} 21 | 22 | ngOnInit(): void { 23 | this.getHero(); 24 | } 25 | 26 | getHero(): void { 27 | const id = +(this.route.snapshot.paramMap.get('id') ?? ''); 28 | this.heroService.getHero(id) 29 | .subscribe(hero => this.hero = hero); 30 | } 31 | 32 | goBack(): void { 33 | this.location.back(); 34 | } 35 | 36 | save(): void { 37 | this.heroService.updateHero(this.hero) 38 | .subscribe(() => this.goBack()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/hero-search/hero-search.component.css: -------------------------------------------------------------------------------- 1 | /* HeroSearch private styles */ 2 | .search-result li { 3 | border-bottom: 1px solid gray; 4 | border-left: 1px solid gray; 5 | border-right: 1px solid gray; 6 | width:195px; 7 | height: 16px; 8 | padding: 5px; 9 | background-color: white; 10 | cursor: pointer; 11 | list-style-type: none; 12 | } 13 | 14 | .search-result li:hover { 15 | background-color: #607D8B; 16 | } 17 | 18 | .search-result li a { 19 | color: #888; 20 | display: block; 21 | text-decoration: none; 22 | } 23 | 24 | .search-result li a:hover { 25 | color: white; 26 | } 27 | .search-result li a:active { 28 | color: white; 29 | } 30 | #search-box { 31 | width: 200px; 32 | height: 20px; 33 | } 34 | 35 | 36 | ul.search-result { 37 | margin-top: 0; 38 | padding-left: 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/app/hero-search/hero-search.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Hero Search

3 | 4 | 5 | 6 | 13 |
14 | -------------------------------------------------------------------------------- /src/app/hero-search/hero-search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { HeroSearchComponent } from './hero-search.component'; 4 | 5 | describe('HeroSearchComponent', () => { 6 | let component: HeroSearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeroSearchComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeroSearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/hero-search/hero-search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Observable, Subject } from 'rxjs'; 4 | 5 | import { 6 | debounceTime, distinctUntilChanged, switchMap 7 | } from 'rxjs/operators'; 8 | 9 | import { Hero } from '../hero'; 10 | import { HeroService } from '../hero.service'; 11 | 12 | @Component({ 13 | selector: 'hero-search', 14 | templateUrl: './hero-search.component.html', 15 | styleUrls: [ './hero-search.component.css' ] 16 | }) 17 | export class HeroSearchComponent implements OnInit { 18 | heroes!: Observable; 19 | private searchTerms = new Subject(); 20 | 21 | constructor(private heroService: HeroService) {} 22 | 23 | // Push a search term into the observable stream. 24 | search(term: string): void { 25 | this.searchTerms.next(term); 26 | } 27 | 28 | ngOnInit(): void { 29 | this.heroes = this.searchTerms.pipe( 30 | // wait 300ms after each keystroke before considering the term 31 | debounceTime(300), 32 | 33 | // ignore new term if same as previous term 34 | distinctUntilChanged(), 35 | 36 | // switch to new search observable each time the term changes 37 | switchMap((term: string) => this.heroService.searchHeroes(term)), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/hero.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, Optional } from '@angular/core'; 2 | import { APP_BASE_HREF } from '@angular/common'; 3 | import { HttpClient, HttpHeaders }from '@angular/common/http'; 4 | 5 | import { Observable, of } from 'rxjs'; 6 | import { catchError, map, tap } from 'rxjs/operators'; 7 | 8 | import { Hero } from './hero'; 9 | import { MessageService } from './message.service'; 10 | 11 | const httpOptions = { 12 | headers: new HttpHeaders({ 'Content-Type': 'application/json' }) 13 | }; 14 | 15 | @Injectable() 16 | export class HeroService { 17 | 18 | private heroesUrl = 'api/heroes'; // URL to web api 19 | 20 | constructor( 21 | private http: HttpClient, 22 | private messageService: MessageService, 23 | @Optional() @Inject(APP_BASE_HREF) origin: string) { 24 | this.heroesUrl = `${origin}${this.heroesUrl}`; 25 | } 26 | 27 | /** GET heroes from the server */ 28 | getHeroes(): Observable { 29 | // return this.http.get(this.heroesUrl) 30 | // .pipe( 31 | // tap(heroes => this.log(`fetched heroes`)), 32 | // catchError(this.handleError('getHeroes', [])) 33 | // ); 34 | return of([ 35 | { id: 1, name: 'bob 1' }, 36 | { id: 2, name: 'bob 2' }, 37 | { id: 3, name: 'bob 3' }, 38 | { id: 3, name: 'bob 3' }, 39 | ] as Hero[]); 40 | } 41 | 42 | /** GET hero by id. Return `undefined` when id not found */ 43 | getHeroNo404(id: number): Observable { 44 | const url = `${this.heroesUrl}/?id=${id}`; 45 | return this.http.get(url) 46 | .pipe( 47 | map(heroes => heroes[0]), // returns a {0|1} element array 48 | tap(h => { 49 | const outcome = h ? `fetched` : `did not find`; 50 | this.log(`${outcome} hero id=${id}`); 51 | }), 52 | catchError(this.handleError(`getHero id=${id}`)) 53 | ); 54 | } 55 | 56 | /** GET hero by id. Will 404 if id not found */ 57 | getHero(id: number): Observable { 58 | const url = `${this.heroesUrl}/${id}`; 59 | return this.http.get(url).pipe( 60 | tap(_ => this.log(`fetched hero id=${id}`)), 61 | catchError(this.handleError(`getHero id=${id}`)) 62 | ); 63 | } 64 | 65 | /* GET heroes whose name contains search term */ 66 | searchHeroes(term: string): Observable { 67 | if (!term.trim()) { 68 | // if not search term, return empty hero array. 69 | return of([]); 70 | } 71 | return this.http.get(`api/heroes/?name=${term}`).pipe( 72 | tap(_ => this.log(`found heroes matching "${term}"`)), 73 | catchError(this.handleError('searchHeroes', [])) 74 | ); 75 | } 76 | 77 | //////// Save methods ////////// 78 | 79 | /** POST: add a new hero to the server */ 80 | addHero (name: string): Observable { 81 | const hero = { name }; 82 | 83 | return this.http.post(this.heroesUrl, hero, httpOptions).pipe( 84 | tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)), 85 | catchError(this.handleError('addHero')) 86 | ); 87 | } 88 | 89 | /** DELETE: delete the hero from the server */ 90 | deleteHero (hero: Hero | number): Observable { 91 | const id = typeof hero === 'number' ? hero : hero.id; 92 | const url = `${this.heroesUrl}/${id}`; 93 | 94 | return this.http.delete(url, httpOptions).pipe( 95 | tap(_ => this.log(`deleted hero id=${id}`)), 96 | catchError(this.handleError('deleteHero')) 97 | ); 98 | } 99 | 100 | /** PUT: update the hero on the server */ 101 | updateHero (hero: Hero): Observable { 102 | return this.http.put(this.heroesUrl, hero, httpOptions).pipe( 103 | tap(_ => this.log(`updated hero id=${hero.id}`)), 104 | catchError(this.handleError('updateHero')) 105 | ); 106 | } 107 | 108 | /** 109 | * Handle Http operation that failed. 110 | * Let the app continue. 111 | * @param operation - name of the operation that failed 112 | * @param result - optional value to return as the observable result 113 | */ 114 | private handleError (operation = 'operation', result?: T) { 115 | return (error: any): Observable => { 116 | 117 | // TODO: send the error to remote logging infrastructure 118 | console.error(error); // log to console instead 119 | 120 | // TODO: better job of transforming error for user consumption 121 | this.log(`${operation} failed: ${error.message}`); 122 | 123 | // Let the app keep running by returning an empty result. 124 | return of(result as T); 125 | }; 126 | } 127 | 128 | /** Log a HeroService message with the MessageService */ 129 | private log(message: string) { 130 | this.messageService.add('HeroService: ' + message); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/app/hero.ts: -------------------------------------------------------------------------------- 1 | export class Hero { 2 | id?: number; 3 | name?: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/heroes/heroes.component.css: -------------------------------------------------------------------------------- 1 | /* HeroesComponent's private CSS styles */ 2 | .heroes { 3 | margin: 0 0 2em 0; 4 | list-style-type: none; 5 | padding: 0; 6 | width: 15em; 7 | } 8 | .heroes li { 9 | position: relative; 10 | cursor: pointer; 11 | background-color: #EEE; 12 | margin: .5em; 13 | padding: .3em 0; 14 | height: 1.6em; 15 | border-radius: 4px; 16 | } 17 | 18 | .heroes li:hover { 19 | color: #607D8B; 20 | background-color: #DDD; 21 | left: .1em; 22 | } 23 | 24 | .heroes a { 25 | color: #888; 26 | text-decoration: none; 27 | position: relative; 28 | display: block; 29 | width: 250px; 30 | } 31 | 32 | .heroes a:hover { 33 | color:#607D8B; 34 | } 35 | 36 | .heroes .badge { 37 | display: inline-block; 38 | font-size: small; 39 | color: white; 40 | padding: 0.8em 0.7em 0 0.7em; 41 | background-color: #607D8B; 42 | line-height: 1em; 43 | position: relative; 44 | left: -1px; 45 | top: -4px; 46 | height: 1.8em; 47 | min-width: 16px; 48 | text-align: right; 49 | margin-right: .8em; 50 | border-radius: 4px 0 0 4px; 51 | } 52 | 53 | .button { 54 | background-color: #eee; 55 | border: none; 56 | padding: 5px 10px; 57 | border-radius: 4px; 58 | cursor: pointer; 59 | cursor: hand; 60 | font-family: Arial; 61 | } 62 | 63 | button:hover { 64 | background-color: #cfd8dc; 65 | } 66 | 67 | button.delete { 68 | position: relative; 69 | left: 194px; 70 | top: -32px; 71 | background-color: gray !important; 72 | color: white; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/app/heroes/heroes.component.html: -------------------------------------------------------------------------------- 1 |

My Heroes {{ 'HELLO' | translate }}

2 | 3 |
4 | 7 | 8 | 11 |
12 | 13 | 22 | -------------------------------------------------------------------------------- /src/app/heroes/heroes.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { HeroesComponent } from './heroes.component'; 4 | 5 | describe('HeroesComponent', () => { 6 | let component: HeroesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeroesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeroesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/heroes/heroes.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Hero } from '../hero'; 4 | import { HeroService } from '../hero.service'; 5 | 6 | @Component({ 7 | selector: 'app-heroes', 8 | templateUrl: './heroes.component.html', 9 | styleUrls: ['./heroes.component.css'] 10 | }) 11 | export class HeroesComponent implements OnInit { 12 | heroes: Hero[] = []; 13 | 14 | constructor(private heroService: HeroService) { } 15 | 16 | ngOnInit() { 17 | this.getHeroes(); 18 | } 19 | 20 | getHeroes(): void { 21 | this.heroService.getHeroes() 22 | .subscribe(heroes => this.heroes = heroes); 23 | } 24 | 25 | add(name: string): void { 26 | name = name.trim(); 27 | if (!name) { return; } 28 | this.heroService.addHero(name) 29 | .subscribe(hero => { 30 | this.heroes.push(hero); 31 | }); 32 | } 33 | 34 | delete(hero: Hero): void { 35 | this.heroService.deleteHero(hero) 36 | .subscribe(() => { 37 | this.heroes = this.heroes.filter(h => h !== hero); 38 | }); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/app/heroes/heroes.module-routing.ts: -------------------------------------------------------------------------------- 1 | import { LocalizeRouterModule } from "@gilsdav/ngx-translate-router"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | import { NgModule } from "@angular/core"; 4 | import { HeroesComponent } from "./heroes.component"; 5 | 6 | const routes: Routes = [ 7 | { path: "", redirectTo: "home", pathMatch: "full" }, 8 | { path: "home", component: HeroesComponent } 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [ 13 | RouterModule.forChild(routes), 14 | LocalizeRouterModule.forChild(routes) 15 | ], 16 | exports: [RouterModule, LocalizeRouterModule] 17 | }) 18 | export class HeroesRoutingModule {} 19 | -------------------------------------------------------------------------------- /src/app/heroes/heroes.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HeroesComponent } from './heroes.component'; 4 | import { HeroesRoutingModule } from './heroes.module-routing'; 5 | import { TranslateModule } from '@ngx-translate/core'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | HeroesComponent 10 | ], 11 | imports: [ 12 | CommonModule, 13 | HeroesRoutingModule, 14 | TranslateModule.forChild() 15 | ], 16 | exports: [], 17 | providers: [], 18 | }) 19 | export class HeroesModule {} 20 | 21 | // export const HeroesModuleTranslated = translateModule(HeroesModule) as NgModuleFactory; 22 | -------------------------------------------------------------------------------- /src/app/heroes2/heroes.component.css: -------------------------------------------------------------------------------- 1 | /* HeroesComponent's private CSS styles */ 2 | .heroes { 3 | margin: 0 0 2em 0; 4 | list-style-type: none; 5 | padding: 0; 6 | width: 15em; 7 | } 8 | .heroes li { 9 | position: relative; 10 | cursor: pointer; 11 | background-color: #EEE; 12 | margin: .5em; 13 | padding: .3em 0; 14 | height: 1.6em; 15 | border-radius: 4px; 16 | } 17 | 18 | .heroes li:hover { 19 | color: #607D8B; 20 | background-color: #DDD; 21 | left: .1em; 22 | } 23 | 24 | .heroes a { 25 | color: #888; 26 | text-decoration: none; 27 | position: relative; 28 | display: block; 29 | width: 250px; 30 | } 31 | 32 | .heroes a:hover { 33 | color:#607D8B; 34 | } 35 | 36 | .heroes .badge { 37 | display: inline-block; 38 | font-size: small; 39 | color: white; 40 | padding: 0.8em 0.7em 0 0.7em; 41 | background-color: #607D8B; 42 | line-height: 1em; 43 | position: relative; 44 | left: -1px; 45 | top: -4px; 46 | height: 1.8em; 47 | min-width: 16px; 48 | text-align: right; 49 | margin-right: .8em; 50 | border-radius: 4px 0 0 4px; 51 | } 52 | 53 | .button { 54 | background-color: #eee; 55 | border: none; 56 | padding: 5px 10px; 57 | border-radius: 4px; 58 | cursor: pointer; 59 | cursor: hand; 60 | font-family: Arial; 61 | } 62 | 63 | button:hover { 64 | background-color: #cfd8dc; 65 | } 66 | 67 | button.delete { 68 | position: relative; 69 | left: 194px; 70 | top: -32px; 71 | background-color: gray !important; 72 | color: white; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/app/heroes2/heroes.component.html: -------------------------------------------------------------------------------- 1 |

My Heroes {{ 'HELLO' | translate }}

2 | 3 |
4 | 7 | 8 | 11 |
12 | 13 | 22 | -------------------------------------------------------------------------------- /src/app/heroes2/heroes.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { HeroesComponent } from './heroes.component'; 4 | 5 | describe('HeroesComponent', () => { 6 | let component: HeroesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeroesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeroesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/heroes2/heroes.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { TranslateModule } from '@ngx-translate/core'; 4 | import { LocalizeRouterModule } from '@gilsdav/ngx-translate-router'; 5 | 6 | import { Hero } from '../hero'; 7 | import { HeroService } from '../hero.service'; 8 | import { NgForOf } from '@angular/common'; 9 | import { RouterLink } from '@angular/router'; 10 | 11 | @Component({ 12 | selector: 'app-heroes-bis', 13 | templateUrl: './heroes.component.html', 14 | styleUrls: ['./heroes.component.css'], 15 | standalone: true, 16 | imports: [ 17 | TranslateModule, 18 | LocalizeRouterModule, 19 | NgForOf, 20 | RouterLink 21 | ] 22 | }) 23 | export class HeroesComponentBis implements OnInit { 24 | heroes: Hero[] = []; 25 | 26 | constructor(private heroService: HeroService) { } 27 | 28 | ngOnInit() { 29 | this.getHeroes(); 30 | } 31 | 32 | getHeroes(): void { 33 | this.heroService.getHeroes() 34 | .subscribe(heroes => this.heroes = heroes); 35 | } 36 | 37 | add(name: string): void { 38 | name = name.trim(); 39 | if (!name) { return; } 40 | this.heroService.addHero(name) 41 | .subscribe(hero => { 42 | this.heroes.push(hero); 43 | }); 44 | } 45 | 46 | delete(hero: Hero): void { 47 | this.heroService.deleteHero(hero) 48 | .subscribe(() => { 49 | this.heroes = this.heroes.filter(h => h !== hero); 50 | }); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/app/heroes2/heroes.module-routing.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from "@angular/router"; 2 | import { HeroesComponentBis } from "./heroes.component"; 3 | 4 | export const routes: Routes = [ 5 | { path: "", redirectTo: "home", pathMatch: "full" }, 6 | { path: "home", component: HeroesComponentBis } 7 | ]; 8 | -------------------------------------------------------------------------------- /src/app/in-memory-data.service.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDbService } from 'angular-in-memory-web-api'; 2 | 3 | export class InMemoryDataService implements InMemoryDbService { 4 | createDb() { 5 | const heroes = [ 6 | { id: 11, name: 'Mr. Nice' }, 7 | { id: 12, name: 'Narco' }, 8 | { id: 13, name: 'Bombasto' }, 9 | { id: 14, name: 'Celeritas' }, 10 | { id: 15, name: 'Magneta' }, 11 | { id: 16, name: 'RubberMan' }, 12 | { id: 17, name: 'Dynama' }, 13 | { id: 18, name: 'Dr IQ' }, 14 | { id: 19, name: 'Magma' }, 15 | { id: 20, name: 'Tornado' } 16 | ]; 17 | return {heroes}; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/message.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { MessageService } from './message.service'; 4 | 5 | describe('MessageService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [MessageService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([MessageService], (service: MessageService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class MessageService { 5 | messages: string[] = []; 6 | 7 | add(message: string) { 8 | this.messages.push(message); 9 | } 10 | 11 | clear() { 12 | this.messages = []; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/messages/messages.component.css: -------------------------------------------------------------------------------- 1 | /* MessagesComponent's private CSS styles */ 2 | h2 { 3 | color: red; 4 | font-family: Arial, Helvetica, sans-serif; 5 | font-weight: lighter; 6 | } 7 | body { 8 | margin: 2em; 9 | } 10 | body, input[text], button { 11 | color: crimson; 12 | font-family: Cambria, Georgia; 13 | } 14 | 15 | button.clear { 16 | font-family: Arial; 17 | background-color: #eee; 18 | border: none; 19 | padding: 5px 10px; 20 | border-radius: 4px; 21 | cursor: pointer; 22 | cursor: hand; 23 | } 24 | button:hover { 25 | background-color: #cfd8dc; 26 | } 27 | button:disabled { 28 | background-color: #eee; 29 | color: #aaa; 30 | cursor: auto; 31 | } 32 | button.clear { 33 | color: #888; 34 | margin-bottom: 12px; 35 | } 36 | -------------------------------------------------------------------------------- /src/app/messages/messages.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Messages

4 | 6 |
{{message}}
7 | 8 |
9 | -------------------------------------------------------------------------------- /src/app/messages/messages.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { MessagesComponent } from './messages.component'; 4 | 5 | describe('MessagesComponent', () => { 6 | let component: MessagesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MessagesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MessagesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/messages/messages.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MessageService } from '../message.service'; 3 | 4 | @Component({ 5 | selector: 'app-messages', 6 | templateUrl: './messages.component.html', 7 | styleUrls: ['./messages.component.css'] 8 | }) 9 | export class MessagesComponent implements OnInit { 10 | 11 | constructor(public messageService: MessageService) {} 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/mock-heroes.ts: -------------------------------------------------------------------------------- 1 | import { Hero } from './hero'; 2 | 3 | export const HEROES: Hero[] = [ 4 | { id: 11, name: 'Mr. Nice' }, 5 | { id: 12, name: 'Narco' }, 6 | { id: 13, name: 'Bombasto' }, 7 | { id: 14, name: 'Celeritas' }, 8 | { id: 15, name: 'Magneta' }, 9 | { id: 16, name: 'RubberMan' }, 10 | { id: 17, name: 'Dynama' }, 11 | { id: 18, name: 'Dr IQ' }, 12 | { id: 19, name: 'Magma' }, 13 | { id: 20, name: 'Tornado' } 14 | ]; 15 | -------------------------------------------------------------------------------- /src/assets/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "HELLO": "hello !", 3 | "ROUTES.heroes": "heroes", 4 | "ROUTES.home": "home", 5 | "ROUTES.dashboard": "dashboard", 6 | "ROUTES.heroesbis": "alone" 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "HELLO": "Bonjour !", 3 | "ROUTES.heroes": "heros", 4 | "ROUTES.home": "accueil", 5 | "ROUTES.dashboard": "dashboard", 6 | "ROUTES.heroesbis": "seul" 7 | } 8 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | locales: 'http://localhost:4000/', 4 | browserDirPath: 'browser' 5 | }; 6 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | locales: 'http://localhost:4200/', 9 | browserDirPath: 'dist/browser' 10 | }; 11 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- 1 |  h& ��(  @������������������71�U61��0-��2.�;������������������������������71�971��71��71��0-��0-��1-��3/�#������������������71�!71�71��71��71��71��0-��0-��0-��0-��1-��3/����������71�U71��OI��VQ��82��71��71��0-��0-��0-��UR��KH��0-��2.�=������71�71����������mh��71��71��0-��0-��[X����������0-��1.�{������71�71��C=����������71��71��0-��0-����������85��0-��0-��������71��71��71����������������������������������0-��0-��1-��������71��71��71��QL��������������������������?<��0-��0-��0-��������71��71��71��71����������;5��:7����������0-��0-��0-��0-�����71�#71��71��71��71��d_������}y����������IG��0-��0-��0-��0-��4/�71�M71��71��71��71��71������������������0-��0-��0-��0-��0-��3/�A71�k71��71��71��71��71��zv����������VS��0-��0-��0-��0-��0-��2.�e71�}71��71��71��71��71��93����������0-��0-��0-��0-��0-��0-��1.�{71�A71��71��71��71��71��71������db��0-��0-��0-��0-��0-��1-��0-�E������71�)71�71��71��71��=7��0-��0-��0-��0-��1-��3/�/���������������������71�A71�61��0-��0-��3.�9�����������������������������������������������( @ �������������������������������������������71�61��2.��3/�?������������������������������������������������������������������������������71�_71��71��71��0-��0-��2.��3/�������������������������������������������������������������������71�;71��71��71��71��71��0-��0-��0-��0-��3.ŭ4/� ������������������������������������������������������71�71��71��71��71��71��71��71��0-��0-��0-��0-��0-��1.��3/lj���������������������������������������������71� 71�71��71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��0-��1.��3/�_������������������������������������71�71��71��71��71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/�;������������������������71�Y71��71��71��71��71��71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/�������������������71��71��71��XS��wr��vr��vr��<7��71��71��71��71��71��0-��0-��0-��0-��0-��0-��wu��~|��~|��NK��0-��0-��2.��������������������71��71��71��UP��������������{w��71��71��71��71��71��0-��0-��0-��0-��0-��YW��������������DB��0-��0-��1.�����������������71�71��71��71��71������������������71��71��71��71��71��0-��0-��0-��0-��0-������������������0-��0-��0-��1.�����������������71�O71��71��71��71��hc��������������SN��71��71��71��71��0-��0-��0-��0-��B?��������������PM��0-��0-��0-��0-��50�������������71�}71��71��71��71��71������������������71��71��71��71��0-��0-��0-��0-������������������0-��0-��0-��0-��0-��3/�3������������71�71��71��71��71��71��~{����������������������������������������������������������][��0-��0-��0-��0-��0-��3/�i������������71��71��71��71��71��71��;5����������������������������������������������������������0-��0-��0-��0-��0-��0-��3/Ǘ������������71��71��71��71��71��71��71������������������������������������������������������mk��0-��0-��0-��0-��0-��0-��2.Ž������������71��71��71��71��71��71��71��D>������������������ie��ie��db��db������������������1.��0-��0-��0-��0-��0-��0-��2.��������������71��71��71��71��71��71��71��71������������������71��71��0-��0-��������������|z��0-��0-��0-��0-��0-��0-��0-��2.��������������71��71��71��71��71��71��71��71��RL��������������JD��71��0-��XU��������������52��0-��0-��0-��0-��0-��0-��0-��1.�����������71�+71��71��71��71��71��71��71��71��71������������������71��0-������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��50�������71�]71��71��71��71��71��71��71��71��71��ea��������������:4��QO��������������:7��0-��0-��0-��0-��0-��0-��0-��0-��0-��4/�5������71�71��71��71��71��71��71��71��71��71��71��������������wr������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��3/�k������71�71��71��71��71��71��71��71��71��71��71��zv��������������������������C@��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��3/Ǘ������71��71��71��71��71��71��71��71��71��71��71��:4��������������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.Ž������71��71��71��71��71��71��71��71��71��71��71��71����������������������MK��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��������71��71��71��71��71��71��71��71��71��71��71��71��B<������������������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��������71��71��71��71��71��71��71��71��71��71��71��71��71��������������[Y��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��1.��������71��71��71��71��71��71��71��71��71��71��71��71��71��OJ����������0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��������71�71�y71��71��71��71��71��71��71��71��71��71��71��71������jg��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/Ɠ4/����������������71�71�71��71��71��71��71��71��71��71��71��PK��1.��0-��0-��0-��0-��0-��0-��0-��0-��2.��3/Ǜ3/����������������������������������71�)71�71��71��71��71��71��71��71��0-��0-��0-��0-��0-��0-��1.��3/ƥ4/�'���������������������������������������������������71�C71�71��71��71��71��0-��0-��0-��1.��3.ů3/�1���������������������������������������������������������������������71�a71��61��1.��2.ŷ3/�=��������������������������������������� -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tour of Heroes 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.server.ts: -------------------------------------------------------------------------------- 1 | // import { enableProdMode } from '@angular/core'; 2 | // export { AppServerModule } from './app/app.server.module'; 3 | 4 | // import { environment } from './environments/environment'; 5 | 6 | // if (environment.production) { 7 | // enableProdMode(); 8 | // } 9 | 10 | 11 | // export { renderModule } from '@angular/platform-server'; 12 | 13 | export { AppServerModule as default } from './app/app.server.module'; 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 | // document.addEventListener('DOMContentLoaded', () => { 12 | // platformBrowserDynamic().bootstrapModule(AppModule) 13 | // .catch(err => console.log(err)); 14 | // }); 15 | 16 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 17 | 18 | import { AppModule } from './app/app.module'; 19 | 20 | 21 | document.addEventListener('DOMContentLoaded', () => { 22 | platformBrowserDynamic().bootstrapModule(AppModule) 23 | .catch(err => console.error(err)); 24 | }); 25 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* Master Styles */ 2 | h1 { 3 | color: #369; 4 | font-family: Arial, Helvetica, sans-serif; 5 | font-size: 250%; 6 | } 7 | h2, h3 { 8 | color: #444; 9 | font-family: Arial, Helvetica, sans-serif; 10 | font-weight: lighter; 11 | } 12 | body { 13 | margin: 2em; 14 | } 15 | body, input[text], button { 16 | color: #888; 17 | font-family: Cambria, Georgia; 18 | } 19 | a { 20 | cursor: pointer; 21 | cursor: hand; 22 | } 23 | button { 24 | font-family: Arial; 25 | background-color: #eee; 26 | border: none; 27 | padding: 5px 10px; 28 | border-radius: 4px; 29 | cursor: pointer; 30 | cursor: hand; 31 | } 32 | button:hover { 33 | background-color: #cfd8dc; 34 | } 35 | button:disabled { 36 | background-color: #eee; 37 | color: #aaa; 38 | cursor: auto; 39 | } 40 | 41 | /* Navigation link styles */ 42 | nav a { 43 | padding: 5px 10px; 44 | text-decoration: none; 45 | margin-right: 10px; 46 | margin-top: 10px; 47 | display: inline-block; 48 | background-color: #eee; 49 | border-radius: 4px; 50 | } 51 | nav a:visited, a:link { 52 | color: #607D8B; 53 | } 54 | nav a:hover { 55 | color: #039be5; 56 | background-color: #CFD8DC; 57 | } 58 | nav a.active { 59 | color: #039be5; 60 | } 61 | 62 | /* everywhere else */ 63 | * { 64 | font-family: Arial, Helvetica, sans-serif; 65 | } 66 | -------------------------------------------------------------------------------- /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/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | 18 | // Prevent Karma from running prematurely. 19 | __karma__.loaded = function () {}; 20 | 21 | // First, initialize the Angular testing environment. 22 | getTestBed().initTestEnvironment( 23 | BrowserDynamicTestingModule, 24 | platformBrowserDynamicTesting(), { 25 | teardown: { destroyAfterEach: false } 26 | } 27 | ); 28 | // Finally, start Karma to run the tests. 29 | __karma__.start(); 30 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [ 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/main.ts", 12 | "src/main.server.ts", 13 | "server.ts" 14 | ], 15 | "include": [ 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "outDir": "./dist/out-tsc", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "esModuleInterop": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "ES2022", 20 | "useDefineForClassFields": false, 21 | "lib": [ 22 | "ES2022", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /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-parens": false, 12 | "arrow-return-shorthand": true, 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "component-class-suffix": true, 18 | "contextual-lifecycle": true, 19 | "directive-class-suffix": true, 20 | "directive-selector": [ 21 | true, 22 | "attribute", 23 | "app", 24 | "camelCase" 25 | ], 26 | "component-selector": [ 27 | true, 28 | "element", 29 | "app", 30 | "kebab-case" 31 | ], 32 | "eofline": true, 33 | "import-blacklist": [ 34 | true, 35 | "rxjs/Rx" 36 | ], 37 | "import-spacing": true, 38 | "indent": { 39 | "options": [ 40 | "spaces" 41 | ] 42 | }, 43 | "interface-name": false, 44 | "max-classes-per-file": false, 45 | "max-line-length": [ 46 | true, 47 | 140 48 | ], 49 | "member-access": false, 50 | "member-ordering": [ 51 | true, 52 | { 53 | "order": [ 54 | "static-field", 55 | "instance-field", 56 | "static-method", 57 | "instance-method" 58 | ] 59 | } 60 | ], 61 | "no-consecutive-blank-lines": false, 62 | "no-console": [ 63 | true, 64 | "debug", 65 | "info", 66 | "time", 67 | "timeEnd", 68 | "trace" 69 | ], 70 | "no-empty": false, 71 | "no-inferrable-types": [ 72 | true, 73 | "ignore-params" 74 | ], 75 | "no-non-null-assertion": true, 76 | "no-redundant-jsdoc": true, 77 | "no-switch-case-fall-through": true, 78 | "no-var-requires": false, 79 | "object-literal-key-quotes": [ 80 | true, 81 | "as-needed" 82 | ], 83 | "object-literal-sort-keys": false, 84 | "ordered-imports": false, 85 | "quotemark": [ 86 | true, 87 | "single" 88 | ], 89 | "semicolon": { 90 | "options": [ 91 | "always" 92 | ] 93 | }, 94 | "space-before-function-paren": { 95 | "options": { 96 | "anonymous": "never", 97 | "asyncArrow": "always", 98 | "constructor": "never", 99 | "method": "never", 100 | "named": "never" 101 | } 102 | }, 103 | "trailing-comma": false, 104 | "no-conflicting-lifecycle": true, 105 | "no-host-metadata-property": true, 106 | "no-input-rename": true, 107 | "no-inputs-metadata-property": true, 108 | "no-output-native": true, 109 | "no-output-on-prefix": true, 110 | "no-output-rename": true, 111 | "no-outputs-metadata-property": true, 112 | "template-banana-in-box": true, 113 | "template-no-negated-async": true, 114 | "typedef-whitespace": { 115 | "options": [ 116 | { 117 | "call-signature": "nospace", 118 | "index-signature": "nospace", 119 | "parameter": "nospace", 120 | "property-declaration": "nospace", 121 | "variable-declaration": "nospace" 122 | }, 123 | { 124 | "call-signature": "onespace", 125 | "index-signature": "onespace", 126 | "parameter": "onespace", 127 | "property-declaration": "onespace", 128 | "variable-declaration": "onespace" 129 | } 130 | ] 131 | }, 132 | "use-lifecycle-interface": true, 133 | "use-pipe-transform-interface": true 134 | , "variable-name": { 135 | "options": [ 136 | "ban-keywords", 137 | "check-format", 138 | "allow-pascal-case" 139 | ] 140 | }, 141 | "whitespace": { 142 | "options": [ 143 | "check-branch", 144 | "check-decl", 145 | "check-operator", 146 | "check-separator", 147 | "check-type", 148 | "check-typecast" 149 | ] 150 | } 151 | }, 152 | "rulesDirectory": [ 153 | "codelyzer" 154 | ] 155 | } 156 | --------------------------------------------------------------------------------