├── .browserslistrc
├── .editorconfig
├── .env.EXAMPLE
├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .prettierrc
├── README.md
├── angular.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── server.ts
├── setup-env.ts
├── src
├── app
│ ├── app-routing.module.ts
│ ├── app.browser.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.server.module.ts
│ ├── guards
│ │ └── auth.guard.ts
│ ├── interceptors
│ │ ├── browser-transfer-state.interceptor.ts
│ │ └── server-transfer-state.interceptor.ts
│ ├── loaders
│ │ ├── translate-browser.loader.ts
│ │ └── translate-server.loader.ts
│ ├── services
│ │ ├── cookies.service.ts
│ │ └── language.service.ts
│ └── utils
│ │ └── domino.utils.ts
├── assets
│ ├── .gitkeep
│ └── i18n
│ │ ├── en-us.json
│ │ └── pt-br.json
├── environments
│ └── .gitkeep
├── favicon.ico
├── index.html
├── main.browser.ts
├── main.server.ts
├── polyfills.ts
├── styles.scss
├── styles
│ └── variables.scss
└── test.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.server.json
└── tsconfig.spec.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.env.EXAMPLE:
--------------------------------------------------------------------------------
1 | PRODUCTION=false
2 | APP_ID=my-app
3 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json"
14 | ],
15 | "createDefaultProgram": true
16 | },
17 | "extends": [
18 | "plugin:@angular-eslint/recommended",
19 | "plugin:@angular-eslint/template/process-inline-templates"
20 | ],
21 | "rules": {
22 | "@angular-eslint/directive-selector": [
23 | "error",
24 | {
25 | "type": "attribute",
26 | "prefix": "app",
27 | "style": "camelCase"
28 | }
29 | ],
30 | "@angular-eslint/component-selector": [
31 | "error",
32 | {
33 | "type": "element",
34 | "prefix": "app",
35 | "style": "kebab-case"
36 | }
37 | ],
38 | "@typescript-eslint/semi": [
39 | "error"
40 | ],
41 | "indent": [
42 | "error",
43 | 2
44 | ],
45 | "keyword-spacing": [
46 | "error",
47 | {
48 | "before": true,
49 | "after": true
50 | }
51 | ],
52 | "space-before-function-paren": [
53 | "error",
54 | {
55 | "anonymous": "never",
56 | "named": "never",
57 | "asyncArrow": "always"
58 | }
59 | ],
60 | "object-curly-spacing": [
61 | "error",
62 | "never"
63 | ],
64 | "array-bracket-spacing": [
65 | "error",
66 | "never"
67 | ],
68 | "space-in-parens": [
69 | "error",
70 | "never"
71 | ],
72 | "space-before-blocks": [
73 | "error"
74 | ],
75 | "comma-spacing": [
76 | 2,
77 | {
78 | "before": false,
79 | "after": true
80 | }
81 | ],
82 | "no-multi-spaces": [
83 | "error"
84 | ],
85 | "quotes": [
86 | "error",
87 | "single"
88 | ],
89 | "key-spacing": [
90 | "error",
91 | {
92 | "beforeColon": false
93 | }
94 | ],
95 | "padded-blocks": [
96 | "error",
97 | "never"
98 | ],
99 | "array-element-newline": [
100 | "error",
101 | "consistent"
102 | ]
103 | }
104 | },
105 | {
106 | "files": [
107 | "*.html"
108 | ],
109 | "extends": [
110 | "plugin:@angular-eslint/template/recommended"
111 | ],
112 | "rules": {}
113 | }
114 | ]
115 | }
116 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 |
16 | # IDEs and editors
17 | /.idea
18 | .project
19 | .classpath
20 | .c9/
21 | *.launch
22 | .settings/
23 | *.sublime-workspace
24 |
25 | # IDE - VSCode
26 | .vscode/*
27 | !.vscode/settings.json
28 | !.vscode/tasks.json
29 | !.vscode/launch.json
30 | !.vscode/extensions.json
31 | .history/*
32 |
33 | # misc
34 | /.angular/cache
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | #environment
45 | /src/environments/environment.ts
46 | .env
47 |
48 | # System Files
49 | .DS_Store
50 | Thumbs.db
51 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts
2 |
3 | node_modules
4 | build
5 | coverage
6 | e2e
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "useTabs": false,
4 | "semi": true,
5 | "bracketSpacing": false,
6 | "singleQuote": true,
7 | "bracketSameLine": true
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Starter Application
2 |
3 | Angular version: `13.1.3`
4 |
5 | ### Preconfigured
6 | ```
7 | - Angular Universal (server side rendering)
8 | - i18n (Multi languages)
9 | - Cache http requests state (server state to client state)
10 | - Cookies
11 | - Dotenv environments setup
12 | - registerLocaleData (pt)
13 | - ESLint
14 | - Prettier
15 | ```
16 |
17 | ### Environments
18 |
19 | - Create a `.env` file in project root;
20 | - Use `.env.EXAMPLE` as example;
21 | - Exec `npm run config` command to generate TS environment file;
22 |
23 | ### i18n - Multi languages
24 |
25 | - There are two preconfigured languages, `pt-br` and `en-us`;
26 | - You can add more creating `.json` files with language short name in `assets/i18n/`;
27 | - To change language in runtime, use `LanguageService` to do this;
28 | - [Read docs](https://github.com/ngx-translate/core)
29 |
30 | ### Cookies
31 |
32 | - Persist data with cookie (Good to use in SSR, for auth, for example);
33 | - Use `CookiesService` to manage cookies;
34 | - [Read docs](https://github.com/salemdar/ngx-cookie)
35 |
36 | ### Cache - Transfer State
37 |
38 | - All success http requests are cached and transferred to client app (to avoid double requests);
39 | - You can make changes in `BrowserStateInterceptor` to improve cache method;
40 |
41 | ### Prettier
42 |
43 | - Exec `npm run prettier` to format files;
44 | - [Read docs](https://prettier.io/docs/en/index.html)
45 |
46 | ### Linter (ESLint)
47 |
48 | - Exec `npm run lint` to analyze files;
49 | - [Read docs](https://github.com/angular-eslint/angular-eslint)
50 |
51 | ### Run dev server
52 |
53 | - Exec `npm run start` to run dev server;
54 | - Exec `npm run start:ssr` to run dev SSR server;
55 |
56 | ### Build production
57 |
58 | #### Client only
59 |
60 | - Exec `npm run build` to build client app only;
61 | - Browser dist files are in `dist/my-app/browser`;
62 |
63 | #### Client and server
64 |
65 | - Exec `npm run build:ssr` to build client and server;
66 | - Browser dist files are in `dist/my-app/browser`;
67 | - Server dist files are in `dist/my-app/server`;
68 |
69 |
70 |
71 | [@gesielrosa](https://gesiel.com)
72 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "my-app": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | },
12 | "@schematics/angular:application": {
13 | "strict": true
14 | }
15 | },
16 | "root": "",
17 | "sourceRoot": "src",
18 | "prefix": "app",
19 | "architect": {
20 | "build": {
21 | "builder": "@angular-devkit/build-angular:browser",
22 | "options": {
23 | "outputPath": "dist/my-app/browser",
24 | "index": "src/index.html",
25 | "main": "src/main.browser.ts",
26 | "polyfills": "src/polyfills.ts",
27 | "tsConfig": "tsconfig.app.json",
28 | "inlineStyleLanguage": "scss",
29 | "assets": [
30 | "src/favicon.ico",
31 | "src/assets"
32 | ],
33 | "styles": [
34 | "src/styles.scss"
35 | ],
36 | "scripts": []
37 | },
38 | "configurations": {
39 | "production": {
40 | "budgets": [
41 | {
42 | "type": "initial",
43 | "maximumWarning": "500kb",
44 | "maximumError": "1mb"
45 | },
46 | {
47 | "type": "anyComponentStyle",
48 | "maximumWarning": "2kb",
49 | "maximumError": "4kb"
50 | }
51 | ],
52 | "outputHashing": "all"
53 | },
54 | "development": {
55 | "buildOptimizer": false,
56 | "optimization": false,
57 | "vendorChunk": true,
58 | "extractLicenses": false,
59 | "sourceMap": true,
60 | "namedChunks": true
61 | }
62 | },
63 | "defaultConfiguration": "production"
64 | },
65 | "serve": {
66 | "builder": "@angular-devkit/build-angular:dev-server",
67 | "configurations": {
68 | "production": {
69 | "browserTarget": "my-app:build:production"
70 | },
71 | "development": {
72 | "browserTarget": "my-app:build:development"
73 | }
74 | },
75 | "defaultConfiguration": "development"
76 | },
77 | "extract-i18n": {
78 | "builder": "@angular-devkit/build-angular:extract-i18n",
79 | "options": {
80 | "browserTarget": "my-app:build"
81 | }
82 | },
83 | "test": {
84 | "builder": "@angular-devkit/build-angular:karma",
85 | "options": {
86 | "main": "src/test.ts",
87 | "polyfills": "src/polyfills.ts",
88 | "tsConfig": "tsconfig.spec.json",
89 | "karmaConfig": "karma.conf.js",
90 | "inlineStyleLanguage": "scss",
91 | "assets": [
92 | "src/favicon.ico",
93 | "src/assets"
94 | ],
95 | "styles": [
96 | "src/styles.scss"
97 | ],
98 | "scripts": []
99 | }
100 | },
101 | "server": {
102 | "builder": "@angular-devkit/build-angular:server",
103 | "options": {
104 | "outputPath": "dist/my-app/server",
105 | "main": "server.ts",
106 | "tsConfig": "tsconfig.server.json",
107 | "inlineStyleLanguage": "scss"
108 | },
109 | "configurations": {
110 | "production": {
111 | "outputHashing": "media",
112 | "fileReplacements": []
113 | },
114 | "development": {
115 | "optimization": false,
116 | "sourceMap": true,
117 | "extractLicenses": false
118 | }
119 | },
120 | "defaultConfiguration": "production"
121 | },
122 | "serve-ssr": {
123 | "builder": "@nguniversal/builders:ssr-dev-server",
124 | "configurations": {
125 | "development": {
126 | "browserTarget": "my-app:build:development",
127 | "serverTarget": "my-app:server:development"
128 | },
129 | "production": {
130 | "browserTarget": "my-app:build:production",
131 | "serverTarget": "my-app:server:production"
132 | }
133 | },
134 | "defaultConfiguration": "development"
135 | },
136 | "prerender": {
137 | "builder": "@nguniversal/builders:prerender",
138 | "options": {
139 | "routes": [
140 | "/"
141 | ]
142 | },
143 | "configurations": {
144 | "production": {
145 | "browserTarget": "my-app:build:production",
146 | "serverTarget": "my-app:server:production"
147 | },
148 | "development": {
149 | "browserTarget": "my-app:build:development",
150 | "serverTarget": "my-app:server:development"
151 | }
152 | },
153 | "defaultConfiguration": "production"
154 | },
155 | "lint": {
156 | "builder": "@angular-eslint/builder:lint",
157 | "options": {
158 | "lintFilePatterns": [
159 | "src/**/*.ts",
160 | "src/**/*.html"
161 | ]
162 | }
163 | }
164 | }
165 | }
166 | },
167 | "defaultProject": "my-app",
168 | "cli": {
169 | "defaultCollection": "@angular-eslint/schematics"
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/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 | const {environment} = require("./src/environments/environment");
5 |
6 | module.exports = function (config) {
7 | config.set({
8 | basePath: '',
9 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
10 | plugins: [
11 | require('karma-jasmine'),
12 | require('karma-chrome-launcher'),
13 | require('karma-jasmine-html-reporter'),
14 | require('karma-coverage'),
15 | require('@angular-devkit/build-angular/plugins/karma')
16 | ],
17 | client: {
18 | jasmine: {
19 | // you can add configuration options for Jasmine here
20 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
21 | // for example, you can disable the random execution with `random: false`
22 | // or set a specific seed with `seed: 4321`
23 | },
24 | clearContext: false // leave Jasmine Spec Runner output visible in browser
25 | },
26 | jasmineHtmlReporter: {
27 | suppressAll: true // removes the duplicated traces
28 | },
29 | coverageReporter: {
30 | dir: require('path').join(__dirname, './coverage/', environment.appId),
31 | subdir: '.',
32 | reporters: [
33 | {type: 'html'},
34 | {type: 'text-summary'}
35 | ]
36 | },
37 | reporters: ['progress', 'kjhtml'],
38 | port: 9876,
39 | colors: true,
40 | logLevel: config.LOG_INFO,
41 | autoWatch: true,
42 | browsers: ['Chrome'],
43 | singleRun: false,
44 | restartOnFileChange: true
45 | });
46 | };
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "config": "ts-node setup-env.ts",
6 | "ng": "ng",
7 | "start": "npm run config && NG_PERSISTENT_BUILD_CACHE=1 ng serve",
8 | "start:ssr": "npm run config && ng run my-app:serve-ssr",
9 | "build": "npm run config && NG_PERSISTENT_BUILD_CACHE=1 ng build",
10 | "build:ssr": "npm run build && ng run my-app:server",
11 | "serve:ssr": "node dist/my-app/server/main.js",
12 | "test": "npm run config && ng test",
13 | "lint": "ng lint",
14 | "prettier": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.scss}\"",
15 | "postinstall": "ngcc"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/animations": "~13.1.3",
20 | "@angular/common": "~13.1.3",
21 | "@angular/compiler": "~13.1.3",
22 | "@angular/core": "~13.1.3",
23 | "@angular/forms": "~13.1.3",
24 | "@angular/platform-browser": "~13.1.3",
25 | "@angular/platform-browser-dynamic": "~13.1.3",
26 | "@angular/platform-server": "~13.1.3",
27 | "@angular/router": "~13.1.3",
28 | "@nguniversal/express-engine": "^13.0.2",
29 | "@ngx-translate/core": "^14.0.0",
30 | "@ngx-translate/http-loader": "^7.0.0",
31 | "cookie-parser": "^1.4.6",
32 | "express": "^4.17.2",
33 | "globalthis": "^1.0.2",
34 | "ngx-cookie": "^5.0.2",
35 | "ngx-cookie-backend": "^5.0.2",
36 | "rxjs": "~7.4.0",
37 | "tslib": "^2.3.0",
38 | "zone.js": "~0.11.4"
39 | },
40 | "devDependencies": {
41 | "@angular-devkit/build-angular": "^13.1.4",
42 | "@angular-eslint/builder": "^13.0.1",
43 | "@angular-eslint/eslint-plugin": "^13.0.1",
44 | "@angular-eslint/eslint-plugin-template": "^13.0.1",
45 | "@angular-eslint/schematics": "^13.0.1",
46 | "@angular-eslint/template-parser": "^13.0.1",
47 | "@angular/cli": "~13.1.4",
48 | "@angular/compiler-cli": "~13.1.3",
49 | "@nguniversal/builders": "^13.0.2",
50 | "@types/cookie-parser": "^1.4.2",
51 | "@types/express": "^4.17.13",
52 | "@types/jasmine": "~3.10.3",
53 | "@types/node": "^12.20.42",
54 | "@typescript-eslint/eslint-plugin": "5.3.0",
55 | "@typescript-eslint/parser": "5.3.0",
56 | "dotenv": "^14.3.0",
57 | "eslint": "^8.2.0",
58 | "jasmine-core": "~3.10.0",
59 | "karma": "~6.3.0",
60 | "karma-chrome-launcher": "~3.1.0",
61 | "karma-coverage": "~2.0.3",
62 | "karma-jasmine": "~4.0.0",
63 | "karma-jasmine-html-reporter": "~1.7.0",
64 | "prettier": "^2.5.1",
65 | "ts-node": "^10.4.0",
66 | "typescript": "~4.4.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/server.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js/dist/zone-node';
2 | import 'globalthis/auto';
3 |
4 | import {ngExpressEngine} from '@nguniversal/express-engine';
5 | import * as express from 'express';
6 | import {join} from 'path';
7 |
8 | import {applyDomino} from '@utils/domino.utils';
9 | import {environment} from '@env/environment';
10 |
11 | import {AppServerModule} from './src/main.server';
12 | import {APP_BASE_HREF} from '@angular/common';
13 | import {existsSync} from 'fs';
14 |
15 | import {REQUEST, RESPONSE} from '@nguniversal/express-engine/tokens';
16 | import * as cookieparser from 'cookie-parser';
17 |
18 | // The Express app is exported so that it can be used by serverless Functions.
19 | export function app(): express.Express {
20 | const server = express();
21 | const distFolder = join(process.cwd(), `dist/${environment?.appId}/browser`);
22 | applyDomino(global, join(distFolder, 'index.html'));
23 | const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
24 |
25 | // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
26 | server.engine('html', ngExpressEngine({
27 | bootstrap: AppServerModule,
28 | }));
29 |
30 | server.set('view engine', 'html');
31 | server.set('views', distFolder);
32 |
33 | // Example Express Rest API endpoints
34 | // server.get('/api/**', (req, res) => { });
35 | // Serve static files from /browser
36 | server.get('*.*', express.static(distFolder, {
37 | maxAge: '1y'
38 | }));
39 |
40 | // All regular routes use the Universal engine
41 | server.get('*', (req, res) => {
42 | res.render(indexHtml, {
43 | req,
44 | providers: [
45 | {
46 | provide: APP_BASE_HREF,
47 | useValue: req.baseUrl
48 | },
49 | {
50 | provide: REQUEST,
51 | useValue: req,
52 | },
53 | {
54 | provide: RESPONSE,
55 | useValue: res,
56 | }
57 | ]
58 | });
59 | });
60 |
61 | return server;
62 | }
63 |
64 | function run(): void {
65 | const port = process.env['PORT'] || 4000;
66 |
67 | // Start up the Node server
68 | const server = app();
69 | server.use(cookieparser());
70 | server.listen(port, () => {
71 | console.log(`Node Express server listening on http://localhost:${port}`);
72 | });
73 | }
74 |
75 | // Webpack will replace 'require' with '__webpack_require__'
76 | // '__non_webpack_require__' is a proxy to Node 'require'
77 | // The below code is to ensure that the server is run only when not requiring the bundle.
78 | declare const __non_webpack_require__: NodeRequire;
79 | const mainModule = __non_webpack_require__.main;
80 | const moduleFilename = mainModule && mainModule.filename || '';
81 | if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
82 | run();
83 | }
84 |
85 | export * from './src/main.server';
86 |
--------------------------------------------------------------------------------
/setup-env.ts:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | // Configure Angular `environment.ts` file path
3 | const targetPath = './src/environments/environment.ts';
4 | // Load node modules
5 | const colors = require('colors');
6 | require('dotenv').config();
7 | // `environment.ts` file structure
8 | const envConfigFile = `export const environment = {
9 | production: ${process.env['PRODUCTION'] || false},
10 | appId: '${process.env['APP_ID'] || 'my-app'}'
11 | };`;
12 | console.log(colors.magenta('The file `environment.ts` will be written with the following content: \n'));
13 | console.log(colors.brightGreen(envConfigFile + ' \n'));
14 | fs.writeFile(targetPath, envConfigFile, (err: any) => {
15 | if (err) {
16 | throw console?.error(err);
17 | } else {
18 | console.log(colors.magenta(`Angular environment.ts file generated correctly at ${targetPath} \n`));
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {RouterModule, Routes} from '@angular/router';
3 |
4 | const routes: Routes = [];
5 |
6 | @NgModule({
7 | imports: [
8 | RouterModule.forRoot(routes, {
9 | initialNavigation: 'enabledBlocking',
10 | scrollPositionRestoration: 'top'
11 | }),
12 | ],
13 | exports: [RouterModule],
14 | })
15 | export class AppRoutingModule {}
16 |
--------------------------------------------------------------------------------
/src/app/app.browser.module.ts:
--------------------------------------------------------------------------------
1 | import {LOCALE_ID, NgModule} from '@angular/core';
2 | import {BrowserTransferStateModule, TransferState} from '@angular/platform-browser';
3 | import {HTTP_INTERCEPTORS, HttpClient} from '@angular/common/http';
4 | import {StateTransferInitializerModule} from '@nguniversal/common';
5 |
6 | import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
7 |
8 | import {AppModule} from './app.module';
9 | import {AppComponent} from './app.component';
10 |
11 | import {translateBrowserLoaderFactory} from '@loaders/translate-browser.loader';
12 | import {BrowserStateInterceptor} from '@interceptors/browser-transfer-state.interceptor';
13 |
14 | @NgModule({
15 | imports: [
16 | AppModule,
17 | StateTransferInitializerModule,
18 | BrowserTransferStateModule,
19 | TranslateModule.forRoot({
20 | loader: {
21 | provide: TranslateLoader,
22 | useFactory: translateBrowserLoaderFactory,
23 | deps: [HttpClient, TransferState],
24 | },
25 | }),
26 | ],
27 | bootstrap: [AppComponent],
28 | providers: [
29 | {
30 | provide: HTTP_INTERCEPTORS,
31 | useClass: BrowserStateInterceptor,
32 | multi: true,
33 | },
34 | {
35 | provide: 'ORIGIN_URL',
36 | useValue: location.origin,
37 | },
38 | {
39 | provide: LOCALE_ID,
40 | useValue: 'pt',
41 | },
42 | ],
43 | })
44 | export class AppBrowserModule {
45 | }
46 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gesielrosa/angular-starter-app/d09f9b00291935b09780e2ddf42a60b390e6ebe7/src/app/app.component.scss
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 |
3 | import {LanguageService} from '@services/language.service';
4 |
5 | @Component({
6 | selector: 'app-root',
7 | templateUrl: './app.component.html',
8 | styleUrls: ['./app.component.scss'],
9 | })
10 | export class AppComponent implements OnInit {
11 | constructor(private _languageService: LanguageService) {}
12 |
13 | public ngOnInit(): void {
14 | this._languageService.init();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {LOCALE_ID, NgModule} from '@angular/core';
2 | import {BrowserModule} from '@angular/platform-browser';
3 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
4 | import {HttpClientModule} from '@angular/common/http';
5 | import {registerLocaleData} from '@angular/common';
6 | import localePt from '@angular/common/locales/pt';
7 |
8 | import {CookieModule} from 'ngx-cookie';
9 |
10 | import {AppRoutingModule} from './app-routing.module';
11 | import {AppComponent} from './app.component';
12 |
13 | import {environment} from '@env/environment';
14 |
15 | registerLocaleData(localePt, 'pt');
16 |
17 | @NgModule({
18 | declarations: [AppComponent],
19 | imports: [
20 | BrowserModule.withServerTransition({appId: environment.appId}),
21 | AppRoutingModule,
22 | BrowserAnimationsModule,
23 | HttpClientModule,
24 | CookieModule.forRoot(),
25 | ],
26 | providers: [{provide: LOCALE_ID, useValue: 'pt'}],
27 | bootstrap: [AppComponent],
28 | })
29 | export class AppModule {}
30 |
--------------------------------------------------------------------------------
/src/app/app.server.module.ts:
--------------------------------------------------------------------------------
1 | import {LOCALE_ID, NgModule} from '@angular/core';
2 | import {ServerModule, ServerTransferStateModule} from '@angular/platform-server';
3 | import {NoopAnimationsModule} from '@angular/platform-browser/animations';
4 | import {TransferState} from '@angular/platform-browser';
5 | import {HTTP_INTERCEPTORS} from '@angular/common/http';
6 |
7 | import {CookieBackendModule} from 'ngx-cookie-backend';
8 | import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
9 |
10 | import {AppModule} from './app.module';
11 | import {AppComponent} from './app.component';
12 |
13 | import {translateServerLoaderFactory} from '@loaders/translate-server.loader';
14 | import {ServerTransferStateInterceptor} from '@interceptors/server-transfer-state.interceptor';
15 |
16 | @NgModule({
17 | imports: [
18 | AppModule,
19 | ServerModule,
20 | NoopAnimationsModule,
21 | ServerTransferStateModule,
22 | TranslateModule.forRoot({
23 | loader: {
24 | provide: TranslateLoader,
25 | useFactory: translateServerLoaderFactory,
26 | deps: [TransferState],
27 | },
28 | }),
29 | CookieBackendModule.forRoot(),
30 | ],
31 | bootstrap: [AppComponent],
32 | providers: [
33 | {
34 | provide: HTTP_INTERCEPTORS,
35 | useClass: ServerTransferStateInterceptor,
36 | multi: true,
37 | },
38 | {
39 | provide: LOCALE_ID,
40 | useValue: 'pt',
41 | },
42 | ],
43 | })
44 | export class AppServerModule {}
45 |
--------------------------------------------------------------------------------
/src/app/guards/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree} from '@angular/router';
3 |
4 | import {Observable} from 'rxjs';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class AuthGuard implements CanActivate {
10 | public canActivate(
11 | route: ActivatedRouteSnapshot,
12 | state: RouterStateSnapshot
13 | ): Observable | Promise | boolean | UrlTree {
14 | // add guard validation here
15 | return true;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/interceptors/browser-transfer-state.interceptor.ts:
--------------------------------------------------------------------------------
1 | import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
2 | import {Injectable} from '@angular/core';
3 | import {makeStateKey, TransferState} from '@angular/platform-browser';
4 | import {Observable, of} from 'rxjs';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class BrowserStateInterceptor implements HttpInterceptor {
10 | constructor(private transferState: TransferState) {
11 | }
12 |
13 | public intercept(req: HttpRequest, next: HttpHandler): Observable> {
14 | if (req.method === 'GET') {
15 | const key = makeStateKey(req.url);
16 | const storedResponse: string = this.transferState.get(key, null);
17 | if (storedResponse) {
18 | const response = new HttpResponse({body: storedResponse, status: 200});
19 | return of(response);
20 | }
21 | }
22 |
23 | return next.handle(req);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/interceptors/server-transfer-state.interceptor.ts:
--------------------------------------------------------------------------------
1 | import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
2 | import {Injectable} from '@angular/core';
3 | import {makeStateKey, TransferState} from '@angular/platform-browser';
4 | import {tap} from 'rxjs/operators';
5 | import {Observable} from 'rxjs';
6 |
7 | @Injectable()
8 | export class ServerTransferStateInterceptor implements HttpInterceptor {
9 | constructor(private _transferState: TransferState) {
10 | }
11 |
12 | public intercept(req: HttpRequest, next: HttpHandler): Observable> {
13 | return next.handle(req).pipe(
14 | tap((event) => {
15 | if (event instanceof HttpResponse && (event.status === 200 || event.status === 202)) {
16 | this._transferState.set(makeStateKey(req.url), event.body);
17 | }
18 | })
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/loaders/translate-browser.loader.ts:
--------------------------------------------------------------------------------
1 | import {Observable} from 'rxjs';
2 | import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
3 | import {HttpClient} from '@angular/common/http';
4 |
5 | import {TranslateLoader} from '@ngx-translate/core';
6 | import {TranslateHttpLoader} from '@ngx-translate/http-loader';
7 |
8 | export class TranslateBrowserLoader implements TranslateLoader {
9 | constructor(private _http: HttpClient, private _transferState: TransferState) {}
10 |
11 | public getTranslation(lang: string): Observable {
12 | const key: StateKey = makeStateKey('transfer-translate-' + lang);
13 | const data = this._transferState.get(key, null);
14 |
15 | if (data) {
16 | return new Observable((observer) => {
17 | observer.next(data);
18 | observer.complete();
19 | });
20 | } else {
21 | return new TranslateHttpLoader(this._http, './assets/i18n/', '.json').getTranslation(lang);
22 | }
23 | }
24 | }
25 |
26 | export function translateBrowserLoaderFactory(httpClient: HttpClient, transferState: TransferState) {
27 | return new TranslateBrowserLoader(httpClient, transferState);
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/loaders/translate-server.loader.ts:
--------------------------------------------------------------------------------
1 | import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
2 |
3 | import {TranslateLoader} from '@ngx-translate/core';
4 | import {join} from 'path';
5 | import {Observable} from 'rxjs';
6 | import * as fs from 'fs';
7 |
8 | import {environment} from '@env/environment';
9 |
10 | export class TranslateServerLoader implements TranslateLoader {
11 | constructor(
12 | private _transferState: TransferState,
13 | private _prefix: string = 'i18n',
14 | private _suffix: string = '.json'
15 | ) {}
16 |
17 | public getTranslation(lang: string): Observable {
18 | return new Observable((observer) => {
19 | const assets_folder = join(process.cwd(), 'dist', environment.appId, 'browser', 'assets', this._prefix);
20 |
21 | const jsonData = JSON.parse(fs.readFileSync(`${assets_folder}/${lang}${this._suffix}`, 'utf8'));
22 |
23 | // Here we save the translations in the transfer-state
24 | const key: StateKey = makeStateKey('transfer-translate-' + lang);
25 | this._transferState.set(key, jsonData);
26 |
27 | observer.next(jsonData);
28 | observer.complete();
29 | });
30 | }
31 | }
32 |
33 | export function translateServerLoaderFactory(transferState: TransferState) {
34 | return new TranslateServerLoader(transferState);
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/services/cookies.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 |
3 | import {CookieService as NgxCookieService} from 'ngx-cookie';
4 |
5 | @Injectable({
6 | providedIn: 'root',
7 | })
8 | export class CookiesService {
9 | constructor(private _cookieService: NgxCookieService) {
10 | }
11 |
12 | public setItem(key: string, value: string): void {
13 | this._cookieService.put(key, value);
14 | }
15 |
16 | public setObjectItem(key: string, value: any): void {
17 | this._cookieService.putObject(key, value);
18 | }
19 |
20 | public getItem(key: string): string {
21 | return this._cookieService.get(key);
22 | }
23 |
24 | public getObjectItem(key: string): any {
25 | return this._cookieService.getObject(key);
26 | }
27 |
28 | public removeItem(key: string): void {
29 | this._cookieService.remove(key);
30 | }
31 |
32 | public removeAll(): void {
33 | this._cookieService.removeAll();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/services/language.service.ts:
--------------------------------------------------------------------------------
1 | import {Inject, Injectable, InjectionToken, PLATFORM_ID} from '@angular/core';
2 | import {DOCUMENT, isPlatformBrowser} from '@angular/common';
3 |
4 | import {REQUEST} from '@nguniversal/express-engine/tokens';
5 | import {TranslateService} from '@ngx-translate/core';
6 | import {Request} from 'express';
7 |
8 | import {CookiesService} from '@services/cookies.service';
9 |
10 | @Injectable({
11 | providedIn: 'root',
12 | })
13 | export class LanguageService {
14 | private _language: string;
15 |
16 | private readonly _isBrowser: boolean;
17 |
18 | constructor(
19 | private _translate: TranslateService,
20 | private _storage: CookiesService,
21 | @Inject(REQUEST) private _request: Request,
22 | @Inject(DOCUMENT) private _document: Document,
23 | @Inject(PLATFORM_ID) private _platform_ID: InjectionToken