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