Nem bízol a kollégáidban? Akkor valamit rosszul csinálsz!
bizalom vezetokent
2023-05-03
Dolgok, amikről a programozók azt hiszik, hogy tudják, de tévednek
programozoi-tevhitek-falsehoods
2023-04-08
A programozóktól meg kell szabadulni
chatgpt no-code low-code
2023-01-22
Miért fontosak a személyes adatok egyáltalán?
privacyaware, privacy
2023-01-07
Egy tanmese a szerény, de tehetséges informatikusról, akit én csak Gézának hívok
privacyaware, privacy
2023-01-02
Beskatulyáznád magad egy termékkel? Gondold át még egyszer!
learning, vendorlockin, risky
2022-12-11
Rekrúterekről és állásajánlatokról
bérsáv, recruiter, relevancia
2022-10-30
"A hídon akkor kell átmenni, amikor odaérünk."​ - YAGNI
yagni, pagni
2022-10-17
"A lényeg, hogy a munka készen legyen!"
agile, estimation, becsles, scrum, home office
2022-08-20
Munka vs. hivatás, a klasszikus dilemma
hr, hobbi, munka, egyensuly, hivatas
2022-07-18
Miért nem fogok nálatok technikai interjún részt venni?
technikai interju, hr, recruit, leet code, coding challenge, take home challenge
2022-07-04
"Hogyan építsek kapcsolati tőkét, ha karriert szeretnék váltani?"
linkedin, tippek, trukkok
2022-06-10
Junior/Medior/Senior, hogyan mérjük?
junior, medior, senior, hr, grade, level, experience
2022-05-09
11 tipp frontendeseknek, hogyan tegyék hatékonyabbá a munkájukat
frondend, vscode, angular
2021-10-31
Motion zoom - mozgás alapú képrekonstrukció
#52het
2021-06-01
Gesture Launcher
#52het
2021-05-31
CellEvent, első Android alkalmazásom
#52het
2021-05-19
Notebookcheck, azaz hogyan válasszuk ki a legjobb ár-érték arányú eszközt
#52het
2021-05-18
Torrent multiplexer
#52het
2021-05-17
Process watcher, logger
#52het
2021-05-05
Lazy loading material dialog content
lazy loading, angular, material, dialog
2020-12-28
Runtime configuration loading in Angular
angular, runtime, configuration, settings, environment, production
2020-03-29
How to start an Angular project?
angular
2020-03-11
Az 52 hét projekt
#52het
2020-01-01
Akkutöltöttség-jelző
#52het
2020-01-01
NetClub - Kollégiumi internetszolgáltató
#52het
2019-12-31
DeeJayy - Lost Terminal
#52het
2019-12-31
Counter Strike monitor
#52het
2019-12-31
Kollégiumi CS bajnokság 2005
#52het
2019-12-31
Mozgásérzékelős képrögzítő, Camera Capture
#52het
2019-12-30
Többszörös host pingelő
#52het
2019-12-30
Sávszélesség mérő, tesztelő
#52het
2019-12-30
Generáljunk hamis adatokat
#52het
2019-12-30
SQL lekérdezések parancssorból, odbc-vel
#52het
2019-12-30
Nonogram generátor (aka. "Fesse feketére")
#52het
2019-12-30
Egyedi chat alkalmazás, kliens és szerver
#52het
2019-12-29
FontSelector - betűtípusválasztó / font preview
#52het
2019-12-28
Saját hálózati kommunikációs segédszoftver - sox
#52het
2019-12-28
Csoportos e-mail küldő szoftver Delphiben
#52het
2019-12-28
Universal Api caller module for Angular 7-9 With NGRX state management
Ngrx, API, Effects, HttpClient
2019-07-02
A leghosszabb projekt
#52het
2019-04-19
Legelső kioszk projektem: Stari Sör Jukebox
#52het
2019-02-28
BPM számláló
#52het
2018-11-01
Assembly féléves beadandók
#52het
2018-11-01
Chatbot before it was cool
#52het
2018-10-30
StartX - Cseréljük ki a windows tálcáját és a Start menüt
#52het
2018-10-28
What? - fájltípus azonosító
#52het
2018-10-27
Transport Tycoon DirectX
#52het
2018-10-26
Diff - fájlösszehasonlító
#52het
2018-10-26
De Facto - Szoftverfelügyelet
#52het
2018-10-25
CD és DVD katalogizáló
#52het
2018-09-24
MP3 segédeszközök
#52het
2018-09-24
Keylogger - azaz billentyűleütés-figyelő és naplózó alkalmazás
#52het
2018-08-29
Az örök projekt: személyes weboldal és blog
#52het
2018-08-14
Szógyakorló nyelvtanuláshoz
#52het
2018-08-13
Warzone 2100 mentett játék szerkesztő
#52het
2018-08-13
Rejtett Windows-beállításokat konfiguráló program: TweakMaster
#52het
2018-08-13
A DrótPostaGalamb levelezőprogram adatfájljainak dekódolása
#52het
2018-08-13
Privacy jegyzet
2018-07-30
Egy éve ilyenkor
2018-07-25
The Matrix - konzol szimuláció
#52het
2018-07-13
Kakaóreceptkönyv
kakaó
2018-06-29
Crackelés!
#52het
2018-06-22
A K.I.T.T. challenge
#52het
2018-06-18
Doom 2 botokkal
#52het
2018-06-16
Römi játék Delphiben
#52het
2018-06-09
MeetsCow & DeeJayy - Intro
#52het
2018-06-09
Direct viewer - bitmap megjelenítő
#52het
2018-06-09
bazMAG
#52het
2018-06-09
Console Vision - Konzolos ablakkezelő Delphiben
#52het
2018-05-01
Quake 2 egy floppyn
#52het
2018-04-20
Játék-kitömörítők
#52het
2018-04-12
Tetszőleges program elrejtése Windows tálcáról
#52het
2018-04-05
Személyre szabás
#52het
2018-03-28
A year with Angular 5, 6 - Angular 2018
#angular5 #angular #resources #articles
2018-03-16
Az ikon evolúciója
#52het
2018-03-14
Betűtípusok
#52het
2018-03-04
Billentyűzet-gyakorló
#52het
2018-02-28
Zenél is a DeeJayy?
#52het
2018-02-22
Térképrajzoló az Ascii 3D labirintushoz
#52het
2018-02-19
Ascii 3D labyrinth
#52het
2018-02-14
52 hét - 52 projekt, avagy #eletem
#52het
2018-02-14
Dockerezzünk virtualizált környezetben!
docker, xen, ubuntu
2017-11-12
A cloud-initramfs-copymods hatásai paravirtualizált környezetben
ubuntu, xen, copymods, docker, docker-ce, docker.io
2017-11-11
Virtualizáljunk Xen 4.6-tal Ubuntu 16-on (Xenial)
ubuntu, xen, virtualizálás, hypervisor, debootstrap
2017-11-04
Álláskeresésem története
álláskeresés, it, fejvadászok, linkedin, job
2017-10-24
FAR Manager competitors
far manager, file managers
2010-01-01
Blog
blog
2001-01-02

How to start an Angular project?

Author: deejayy, 2020-03-11, updated: 2020-10-20

Easy, just "ng new", innit?

ng new angular10-project-init

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS [ https://sass-lang.com/documentation/syntax#scss ]

Official documentation of ng new.

Now you'll have an Angular project set up:

  • node_modules prepared with necessary packages
  • git set up for version tracking with an initial commit
  • project files under "src" directory

This is sort of a checklist.

README.md

It is important to let the others (and by others you can mean yourself 2 years later) know what is this project about. Feel free to extend the already prepared README.md file with specific information about the application.

How-to README?

Working environment

Editor config

Create or extend the .editorconfig file with the following content:

# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single

[*.md]
max_line_length = off
trim_trailing_whitespace = false

[*.html]
quote_type = double

You might want to install the corresponding plugin in your IDE (eg. "Editorconfig for VSCode")

More on editorconfig on the official page.

Github setup (optional)

If you are using github, you can prepare templates for verious activities in the .github directory:

  • contributing.md: what the others should know about how to contribute
  • pull_request_template.md: an initial comment template for every pull request (eg. a brief description about the merging process)

Check github's guidelines on the topic.

AWS (optional)

Are you going to deploy in an AWS CloudFront environment? The CodePipeline may need a specification in the buildspec.yml file:

version: 0.2

phases:
  install:
    commands:
      - echo "Installing packages."
      - npm install
      - echo "Installation of packages is done"
  pre_build:
    commands:
      - echo "Placeholder for pre_build"
  build:
    commands:
      - echo "Build started on /Project Name Here/"
      - npm run build -- --configuration=${ENV}
      - echo "Build completed on /Project Name Here/"
  post_build:
    commands:
      - echo "Copying directory app to to s3 bucket ${BUCKET_NAME}"
      - aws s3 sync dist/ "s3://${BUCKET_NAME}/" --delete --sse
      - aws configure set preview.cloudfront true && aws cloudfront create-invalidation --distribution-id "${DISTRIBUTION_ID}" --paths /\*
      - echo "Post build finihsed"
artifacts:
  files:
    - dist/**/*
    - dist/*

How the buildspec file should look like according to AWS.

Project specific

Browser compatibility

Check browserslist if you have to support older browsers as well, here are some hints about how it works.

Testing with Jest

But why Jest? Because it is faster!

npm install --save-dev @angular-builders/jest @types/jest jest jest-html-reporter jest-preset-angular

Create jest.config.js with the following content:

module.exports = {
  preset: "jest-preset-angular",
  globals: {
    "ts-jest": {
      tsConfig: "<rootDir>/tsconfig.spec.json",
      stringifyContentPathRegex: "\\.html$",
      diagnostics: false,
    },
  },
  setupFilesAfterEnv: ["<rootDir>/src/setupJest.ts"],
  moduleNameMapper: {
    "@app/(.*)": "<rootDir>/src/app/$1",
    "@env/(.*)": "<rootDir>/src/environments/$1",
  },
};

Change the angular.json / projects.<project name>.architect.test block to the following:

"test": {
  "builder": "@angular-builders/jest:run",
  "options": {
    "no-cache": true,
    "reporters": []
  }
},

Add new files for jest setup:

src/jestGlobalMocks.ts

export function storageMock() {
  const storage = {};

  return {
    setItem: (key: string, value?: any) => {
      storage[key] = value || '';
    },
    getItem: (key: string) => {
      return key in storage ? storage[key] : null;
    },
    removeItem: (key: string) => {
      delete storage[key];
    },
    get length() {
      return Object.keys(storage).length;
    },
    key: (index: number) => {
      const keys = Object.keys(storage);
      return keys[index] || null;
    },
  };
}

Object.defineProperty(window, 'localStorage', { value: storageMock });
Object.defineProperty(window, 'sessionStorage', { value: storageMock });
Object.defineProperty(window, 'gtag', { value: () => {} });
Object.defineProperty(window, 'ga', { value: () => {} });

src/jestGlobalMocks.spec.ts

import { storageMock } from './jestGlobalMocks';

describe('JestMocks', () => {
  it('localStorage must be empty', () => {
    const localStorage = storageMock();
    expect(localStorage.length).toEqual(0);
  });

  it('localStorage set item / get item', () => {
    const localStorage = storageMock();

    localStorage.setItem('key-empty');
    localStorage.setItem('key-number', 1);
    localStorage.setItem('key-string', 'value');
    localStorage.setItem('key-array', [ 2 ]);
    localStorage.setItem('key-object', { a: 'b' });

    expect(localStorage.getItem('key-not-found')).toEqual(null);
    expect(localStorage.getItem('key-empty')).toEqual('');
    expect(localStorage.getItem('key-number')).toEqual(1);
    expect(localStorage.getItem('key-string')).toEqual('value');
    expect(localStorage.getItem('key-array')).toEqual([ 2 ]);
    expect(localStorage.getItem('key-object')).toEqual({ a: 'b' });
  });

  it('localStorage set item / remove item', () => {
    const localStorage = storageMock();

    localStorage.setItem('key-number', 1);
    expect(localStorage.getItem('key-number')).toEqual(1);
    localStorage.removeItem('key-number');
    expect(localStorage.getItem('key-number')).toEqual(null);
  });

  it('localStorage set item / key', () => {
    const localStorage = storageMock();

    localStorage.setItem('key-number', 1);
    expect(localStorage.key(0)).toEqual('key-number');
    expect(localStorage.key(1)).toEqual(null);
  });
});

src/setupJest.ts

import 'jest-preset-angular';
import './jestGlobalMocks';

Now try out jest

jest jestGlobalMocks
 PASS  src/jestGlobalMocks.spec.ts
  JestMocks
    √ localStorage must be empty (3ms)
    √ localStorage set item / get item (2ms)
    √ localStorage set item / remove item (1ms)
    √ localStorage set item / key

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        2.771s, estimated 4s
Ran all test suites matching /jestGlobalMocks/i.

Module path aliases

If you see "../" in any TypeScript file, you're doomed. Relative paths will cause a lot of headaches later. The solution is: module path aliases! Check out this intro about it!

There are two places where you need to maintain the list of module path aliases.

  1. package.json (for Jest, see above)
  2. tsconfig.json (for build, see below)

Check tsconfig.json / compilerOptions branch and add this:

"paths": {
  "@app/*": ["src/app/*"],
  "@env/*": ["src/environments/*"]
},

Version number

Open package.json and set the version value.

Trick: you can add this line to every environment.ts if you want to advertise the version number runtime:

import { version } from '../../package.json';

export const environment = {
  production: false,
  version: version,
};

Don't forget about letting the typescript compiler know that you want to import a json in a TS file, add these two lines to your tsconfig.json / compilerOptions:

"resolveJsonModule": true,
"esModuleInterop": true

You can add this version number to eg. some online tracking tool, like Google Analytics (check angulartics2 module).

Static code checking tools

Lint-staged, stylelint and commit hook. Because static check can improve the code quality significantly.

npm install --save-dev lint-staged husky stylelint stylelint-config-standard-scss stylelint-scss tslint tslint-clean-code tslint-microsoft-contrib

Add the following items to the package.json / scripts section:

    "lint": "ng lint && npm run lint:styles",
    "lint:styles": "stylelint --syntax=scss --config=./stylelint **/*.scss",
    "lint-staged": "lint-staged -r",
    "precommit": "npm run lint-staged"

... and this blocks to root:

  "lint-staged": {
    "*.ts": [
      "tslint",
      "jest --findRelatedTests"
    ],
    "*.scss": "stylelint --syntax=scss --config=./stylelint"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },

stylelint.json

{
  "extends": "stylelint-config-standard-scss",
  "rules": {
    "at-rule-empty-line-before": [
      "always",
      {
        "except": [
          "blockless-after-same-name-blockless",
          "blockless-after-blockless",
          "first-nested"
        ],
        "ignoreAtRules": [
          "import",
          "include"
        ]
      }
    ],
    "at-rule-no-vendor-prefix": true,
    "at-rule-allowed-list": [
      "mixin",
      "if",
      "function",
      "return",
      "import",
      "include",
      "media",
      "keyframes",
      "supports",
      "font-face"
    ],
    "block-no-empty": [
      true,
      {
        "ignore": [
          "comments"
        ]
      }
    ],
    "color-no-invalid-hex": true,
    "comment-no-empty": true,
    "declaration-block-no-duplicate-properties": true,
    "declaration-block-no-shorthand-property-overrides": true,
    "declaration-empty-line-before": "never",
    "font-family-no-duplicate-names": true,
    "font-family-no-missing-generic-family-keyword": true,
    "function-calc-no-invalid": true,
    "function-calc-no-unspaced-operator": true,
    "function-linear-gradient-no-nonstandard-direction": true,
    "keyframe-declaration-no-important": true,
    "media-feature-name-no-unknown": true,
    "no-descending-specificity": true,
    "no-duplicate-at-import-rules": true,
    "no-duplicate-selectors": true,
    "no-extra-semicolons": true,
    "no-invalid-double-slash-comments": true,
    "property-no-unknown": [
      true,
      {
        "ignoreProperties": [
          "scroll",
          "overflow-x",
          "overflow-y"
        ]
      }
    ],
    "property-no-vendor-prefix": [
      true,
      {
        "ignoreProperties": [
          "appearance"
        ]
      }
    ],
    "rule-empty-line-before": [
      "always",
      {
        "except": [
          "after-single-line-comment",
          "first-nested"
        ]
      }
    ],
    "selector-pseudo-class-no-unknown": true,
    "selector-pseudo-element-no-unknown": true,
    "selector-no-vendor-prefix": true,
    "selector-type-no-unknown": true,
    "string-no-newline": true,
    "unit-no-unknown": true
  }
}

Try it out with the command

npm run lint

tslint.json

{
  "rulesDirectory": [
    "tslint-microsoft-contrib",
    "codelyzer",
    "tslint-clean-code"
  ],
  "rules": {
    "align": [
      false
    ],
    "array-type": [
      true,
      "array"
    ],
    "arrow-return-shorthand": [true, "multiline"],
    "await-promise": false,
    "adjacent-overload-signatures": false,
    "arrow-parens": true,
    "ban": false,
    "callable-types": true,
    "class-name": true,
    "comment-format": [
      false
    ],
    "completed-docs": [
      false
    ],
    "curly": true,
    "cyclomatic-complexity": [
      true,
      6
    ],
    "deprecation": {
      "severity": "warning"
    },
    "eofline": true,
    "export-name": false,
    "file-header": [
      false
    ],
    "forin": true,
    "function-name": [
      true,
      {
        "static-method-regex": "^[A-Za-z_\\d]+$"
      }
    ],
    "import-blacklist": [
      true,
      "rxjs/Rx"
    ],
    "import-spacing": true,
    "indent": [true, "spaces", 2],
    "insecure-random": true,
    "interface-name": false,
    "interface-over-type-literal": false,
    "jquery-deferred-must-complete": true,
    "jsdoc-format": false,
    "label-position": true,
    "linebreak-style": [
      true,
      "LF"
    ],
    "max-classes-per-file": false,
    "max-file-line-count": [
      false
    ],
    "max-func-body-length": [
      true,
      100,
      {
        "ignore-parameters-to-function-regex": "describe"
      }
    ],
    "max-line-length": [
      true,
      140
    ],
    "member-access": true,
    "member-ordering": [
      false
    ],
    "missing-jsdoc": false,
    "missing-optional-annotation": false,
    "min-class-cohesion": false,
    "new-parens": true,
    "newspaper-order": false,
    "no-angle-bracket-type-assertion": false,
    "no-any": true,
    "no-arg": true,
    "no-backbone-get-set-outside-model": false,
    "no-banned-terms": true,
    "no-bitwise": true,
    "no-boolean-literal-compare": false,
    "no-conditional-assignment": true,
    "no-consecutive-blank-lines": [
      true
    ],
    "no-console": [
      true,
      "debug",
      "info",
      "log",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-constant-condition": true,
    "no-construct": true,
    "no-control-regex": true,
    "no-debugger": true,
    "no-default-export": true,
    "no-delete-expression": true,
    "no-disable-auto-sanitization": true,
    "no-document-domain": true,
    "no-document-write": true,
    "no-duplicate-switch-case": true,
    "no-duplicate-parameter-names": true,
    "no-duplicate-super": true,
    "no-duplicate-variable": true,
    "no-empty": false,
    "no-empty-interface": false,
    "no-empty-line-after-opening-brace": true,
    "no-eval": true,
    "no-exec-script": true,
    "no-floating-promises": false,
    "no-for-in": true,
    "no-for-in-array": true,
    "no-function-expression": true,
    "no-http-string": [
      true,
      "http://localhost"
    ],
    "no-import-side-effect": false,
    "no-inferrable-types": false,
    "no-inferred-empty-object-type": false,
    "no-inner-html": true,
    "no-internal-module": true,
    "no-invalid-regexp": true,
    "no-invalid-template-strings": true,
    "no-invalid-this": true,
    "no-jquery-raw-elements": true,
    "no-magic-numbers": true,
    "no-mergeable-namespace": false,
    "no-missing-visibility-modifiers": true,
    "no-misused-new": true,
    "no-multiline-string": false,
    "no-multiple-var-decl": true,
    "no-namespace": false,
    "no-null-keyword": false,
    "no-octal-literal": true,
    "no-parameter-properties": true,
    "no-reference": true,
    "no-reference-import": true,
    "no-regex-spaces": true,
    "no-require-imports": true,
    "no-reserved-keywords": false,
    "no-shadowed-variable": true,
    "no-single-line-block-comment": true,
    "no-sparse-arrays": true,
    "no-stateless-class": false,
    "no-string-based-set-immediate": true,
    "no-string-based-set-interval": true,
    "no-string-based-set-timeout": true,
    "no-string-literal": true,
    "no-string-throw": true,
    "no-suspicious-comment": true,
    "no-switch-case-fall-through": true,
    "no-trailing-whitespace": true,
    "no-typeof-undefined": true,
    "no-unbound-method": false,
    "no-unnecessary-callback-wrapper": true,
    "no-unnecessary-field-initialization": true,
    "no-unnecessary-initializer": true,
    "no-unnecessary-local-variable": true,
    "no-unnecessary-override": true,
    "no-unnecessary-qualifier": false,
    "no-unnecessary-semicolons": true,
    "no-unsafe-any": false,
    "no-unsafe-finally": true,
    "no-unsupported-browser-code": false,
    "no-unused-expression": true,
    "no-var-keyword": true,
    "no-var-requires": true,
    "no-this-assignment": true,
    "no-void-expression": false,
    "no-with-statement": true,
    "non-literal-require": true,
    "object-literal-key-quotes": [
      true,
      "as-needed"
    ],
    "object-literal-shorthand": false,
    "object-literal-sort-keys": false,
    "one-line": [
      true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "one-variable-per-declaration": true,
    "only-arrow-functions": [
      true,
      "allow-declarations",
      "allow-named-functions"
    ],
    "ordered-imports": [
      false
    ],
    "possible-timing-attack": true,
    "prefer-array-literal": true,
    "prefer-const": true,
    "prefer-for-of": false,
    "prefer-function-over-method": false,
    "prefer-method-signature": true,
    "prefer-template": true,
    "strict-type-predicates": false,
    "promise-function-async": false,
    "promise-must-complete": true,
    "quotemark": [
      true,
      "single",
      "avoid-escape"
    ],
    "radix": false,
    "restrict-plus-operands": true,
    "semicolon": [
      true,
      "always"
    ],
    "space-before-function-paren": false,
    "strict-boolean-expressions": false,
    "switch-default": false,
    "trailing-comma": [
      true,
      {
        "singleline": "never",
        "multiline": {
          "objects": "always",
          "arrays": "always",
          "functions": "always",
          "imports": "always",
          "exports": "always",
          "typeLiterals": "always"
        }
      }
    ],
    "triple-equals": [
      true,
      "allow-null-check"
    ],
    "typedef": [
      true,
      "call-signature",
      "parameter",
      "property-declaration"
    ],
    "typedef-whitespace": [
      false
    ],
    "variable-name": {
      "options": [
        "ban-keywords",
        "check-format",
        "allow-pascal-case"
      ]
    },
    "whitespace": {
      "options": [
        "check-branch",
        "check-decl",
        "check-operator",
        "check-separator",
        "check-type",
        "check-typecast"
      ]
    },
    "underscore-consistent-invocation": true,
    "unified-signatures": true,
    "use-isnan": true,
    "use-named-parameter": true,
    "no-useless-files": true,
    "component-class-suffix": true,
    "contextual-lifecycle": true,
    "directive-class-suffix": true,
    "no-conflicting-lifecycle": true,
    "no-host-metadata-property": true,
    "no-input-rename": true,
    "no-inputs-metadata-property": true,
    "no-output-native": true,
    "no-output-on-prefix": true,
    "no-output-rename": true,
    "no-outputs-metadata-property": true,
    "template-banana-in-box": true,
    "template-no-negated-async": true,
    "use-lifecycle-interface": true,
    "use-pipe-transform-interface": true,
    "prefer-on-push-component-change-detection": true,
    "directive-selector": [
      true,
      "attribute",
      "app",
      "camelCase"
    ],
    "component-selector": [
      true,
      "element",
      "app",
      "kebab-case"
    ]
  }
}

Try it out with the command

tslint --project .

Prepare the app

CSS reset

npm install modern-css-reset

Add reset css to the build process: angular.json / projects.<project name>.architect.build.options.styles

"styles": [
  "node_modules/modern-css-reset/dist/reset.css",
  "src/styles.scss"
],

Thanks to Andy for the modern css reset.

Application theme and utils (css)

Some useful mixins for responsive design

src/mixins.scss

$breakpoints: (
  "phone": 400px,
  "phone-wide": 480px,
  "phablet": 560px,
  "tablet-small": 640px,
  "tablet": 768px,
  "tablet-wide": 1024px,
  "desktop": 1248px,
  "desktop-wide": 1440px
);

@mixin mq($width, $type: max) {
  @if map_has_key($breakpoints, $width) {
    $width: map_get($breakpoints, $width);

    @if $type == max {
      $width: $width - 1px;
    }

    @media only screen and (#{$type}-width: $width) {
      @content;
    }
  }
}

@function smart-scale($minwidth, $maxwwidth, $minscreen: 320, $maxscreen: 1440) {
  @return calc(#{$minwidth + 'px'} + (#{$maxwwidth} - #{$minwidth}) * ((100vw - #{$minscreen + 'px'}) / (#{$maxscreen} - #{$minscreen})));
}

The mq mixin will allow you to shorten the usual media query definitions in css, also adds some readable definitions for breakpoints which you can adjust later in a single place.

The smart-scale magic is to make a specific size relative between the minscreen and maxscreen in proportion with the minwidth and maxwidth. It can be used to scale font sizes, box widths, anything what you want to define in pixels.

Application theme with the colors and fonts, and all you want to put in. Use hsl color codes for better readability.

src/app-theme.scss

@import "mixins.scss";

$theme-color-1:  hsl(200, 80, 75%);
$theme-color-2:  hsl(200, 80, 70%);
$theme-color-3:  hsl(200, 80, 65%);
$theme-color-4:  hsl(200, 80, 60%);
$theme-color-5:  hsl(200, 80, 55%);
$theme-color-6:  hsl(200, 80, 50%);
$theme-color-7:  hsl(200, 80, 45%);
$theme-color-8:  hsl(200, 80, 40%);
$theme-color-9:  hsl(200, 80, 35%);
$theme-color-10: hsl(200, 80, 30%);

$theme-color: $theme-color-5;

$theme-light: $theme-color-9;
$theme-default: $theme-color;
$theme-hover: $theme-color-4;
$theme-pressed: $theme-color-2;

$theme-background: hsl(200, 100, 100%);

$warn-color-1:  hsl(0, 80, 75%);
$warn-color-2:  hsl(0, 80, 70%);
$warn-color-3:  hsl(0, 80, 65%);
$warn-color-4:  hsl(0, 80, 60%);
$warn-color-5:  hsl(0, 80, 55%);
$warn-color-6:  hsl(0, 80, 50%);
$warn-color-7:  hsl(0, 80, 45%);
$warn-color-8:  hsl(0, 80, 40%);
$warn-color-9:  hsl(0, 80, 35%);
$warn-color-10: hsl(0, 80, 30%);

$warn-color: $warn-color-5;

$warn-light: $warn-color-9;
$warn-default: $warn-color;
$warn-hover: $warn-color-4;
$warn-pressed: $warn-color-2;

$theme-font: Tahoma, sans-serif;

$black: rgba(0, 0, 0, 1);
$white: rgba(255, 255, 255, 1);

Add your theme to src/styles.scss:

@import "app-theme.scss";

You'll unfortunately need this import in every component's stylesheet, for that, this snippet is needed in the angular.json in order to make it work (place it under projects.<project name>.architect.build.options):

"stylePreprocessorOptions": {
  "includePaths": [
    "src"
  ]
},

Angular Material (optional)

npm install @angular/cdk @angular/material hammerjs

Add BrowserAnimationsModule to app.module.ts

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

...

imports: [
  ...,
  BrowserAnimationsModule,
],

Add basic fonts to index.html

<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Add hammerjs to main.ts

import 'hammerjs';

Create an scss file for material theme:

src/material-theme.scss

@import "app-theme.scss";
@import "~@angular/material/theming";

@include mat-core();

$mat-app: (
  50:  $theme-color-1,
  100: $theme-color-2,
  200: $theme-color-3,
  300: $theme-color-4,
  400: $theme-color-5,
  500: $theme-color-6,
  600: $theme-color-7,
  700: $theme-color-8,
  800: $theme-color-9,
  900: $theme-color-10,
  A100: $theme-light,
  A200: $theme-default,
  A400: $theme-hover,
  A700: $theme-pressed,
  contrast: (
    50: $black,
    100: $black,
    200: $black,
    300: $black,
    400: $white,
    500: $white,
    600: $white,
    700: $white,
    800: $white,
    900: $white,
    A100: $black,
    A200: $white,
    A400: $white,
    A700: $white
  )
);

$mat-app-warn: (
  50: $warn-color-1,
  100: $warn-color-2,
  200: $warn-color-3,
  300: $warn-color-4,
  400: $warn-color-5,
  500: $warn-color-6,
  600: $warn-color-7,
  700: $warn-color-8,
  800: $warn-color-9,
  900: $warn-color-10,
  A100: $warn-light,
  A200: $warn-default,
  A400: $warn-hover,
  A700: $warn-pressed,
  contrast: (
    50: $black,
    100: $black,
    200: $black,
    300: $black,
    400: $black,
    500: $white,
    600: $white,
    700: $white,
    800: $white,
    900: $white,
    A100: $black,
    A200: $white,
    A400: $white,
    A700: $white
  )
);

$app-theme: mat-palette($mat-app);
$app-accent: mat-palette($mat-app, A200, A100, A400);
$app-warn: mat-palette($mat-app-warn, A200, A100, A400);
$app-theme: mat-light-theme($app-theme, $app-accent, $app-warn);

$custom-typography: mat-typography-config(
  $font-family: $theme-font,
  $headline: mat-typography-level(32px, 48px, 800),
  $caption: mat-typography-level(16px, 24px, 400),
  $body-1: mat-typography-level(16px, 24px, 400),
  $body-2: mat-typography-level(16px, 24px, 600)
);

@include angular-material-theme($app-theme);

body {
  color: $theme-color;
  font-size: 16px;
  font-family: $theme-font;
}

Add your material theme to styles.scss (replace the import of app-theme.scss):

@import "material-theme.scss";

Get rid of placeholders

Adjust app.component.html | ts | spec.ts files, remove placeholders, invalid test cases.

Run the first static check

And also fix test errors, if any

npm run lint
jest
ng build --prod

Take care of multiple environments (optional)

If you have other environments apart from local (environment.ts) and production (environment.prod.ts), create them and add them to angular.json as well.

Example:

"dev": {
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.dev.ts"
    }
  ]
},
"staging": {
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.staging.ts"
    }
  ],
  "optimization": true,
  "outputHashing": "all",
  "sourceMap": false,
  "extractCss": true,
  "namedChunks": false,
  "aot": true,
  "extractLicenses": true,
  "vendorChunk": false,
  "buildOptimizer": true
},

Recommended packages

Directory structure

Create the directory structure as per the article:

https://itnext.io/choosing-a-highly-scalable-folder-structure-in-angular-d987de65ec7