First steps for a new angular application

In the last weeks I dig deeper into Angular and how to structure such an application properly incl. the tools needed. So I want to share my first steps I take starting a new Angular app:

Prettier

What is Prettier?

  • An opinionated code formatter
  • Supports many languages
  • Integrates with most editors
  • Has few options

Installation

npm install prettier -D

Configuration

Add .prettierrc file to your project:

{
  "tabWidth": 2,
  "useTabs": false,
  "singleQuote": true,
  "semi": true,
  "quoteProps": "as-needed",
  "trailingComma": "es5",
  "bracketSpacing": true,
  "bracketSameLine": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "printWidth": 100,
  "htmlWhitespaceSensitivity": "ignore"
}

And also install a plugin to your IDE to use it.

I also add a script for prettier to my package.json:

"prettier": "prettier --write src/**/*.\\{ts,scss,html\\}"

This allows me to run npm run prettier to format all my source files.

ESLint

ESLint statically analyzes your code to quickly find problems. ESLint is built into most text editors and you can run ESLint as part of your continuous integration pipeline.

Many problems ESLint finds can be automatically fixed. ESLint fixes are syntax-aware so you won’t experience errors introduced by traditional find-and-replace algorithms.

Preprocess code, use custom parsers, and write your own rules that work alongside ESLint’s built-in rules. You can customize ESLint to work exactly the way you need it for your project.

Installation

ng add @angular-eslint/schematics

Configuration

Change .eslintrc.json to this:

{
  "root": true,
  "ignorePatterns": [
    "projects/**/*"
  ],
  "overrides": [
    {
      "files": [
        "*.ts"
      ],
      "parser": "@typescript-eslint/parser",
      "parserOptions": {
        "project": [
          "tsconfig.json",
        ],
        "createDefaultProgram": true
      },
      "plugins": [
        "@typescript-eslint"
      ],
      "extends": [
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates",
        "plugin:@typescript-eslint/recommended"
      ],
      "rules": {
        "@angular-eslint/component-selector": [
          "error",
          {
            "prefix": "app",
            "style": "kebab-case",
            "type": "element"
          }
        ],
        "@angular-eslint/directive-selector": [
          "error",
          {
            "prefix": "app",
            "style": "camelCase",
            "type": "attribute"
          }
        ],
        "@typescript-eslint/quotes": [
          "error",
          "single",
          {
            "allowTemplateLiterals": true
          }
        ],
        "@angular-eslint/no-empty-lifecycle-method": "warn",
        "@angular-eslint/no-conflicting-lifecycle": "error",
        "@angular-eslint/no-host-metadata-property": "error",
        "@angular-eslint/no-input-rename": "error",
        "@angular-eslint/no-inputs-metadata-property": "error",
        "@angular-eslint/no-output-native": "error",
        "@angular-eslint/no-output-on-prefix": "error",
        "@angular-eslint/no-output-rename": "error",
        "@angular-eslint/no-outputs-metadata-property": "error",
        "@angular-eslint/use-lifecycle-interface": "error",
        "@angular-eslint/use-pipe-transform-interface": "error",
        "@typescript-eslint/adjacent-overload-signatures": "error",
        "@typescript-eslint/array-type": "off",
        "@typescript-eslint/ban-types": [
          "error",
          {
            "types": {
              "Object": {
                "message": "Avoid using the `Object` type. Did you mean `object`?"
              },
              "Function": {
                "message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
              },
              "Boolean": {
                "message": "Avoid using the `Boolean` type. Did you mean `boolean`?"
              },
              "Number": {
                "message": "Avoid using the `Number` type. Did you mean `number`?"
              },
              "String": {
                "message": "Avoid using the `String` type. Did you mean `string`?"
              },
              "Symbol": {
                "message": "Avoid using the `Symbol` type. Did you mean `symbol`?"
              }
            }
          }
        ],
        "@typescript-eslint/consistent-type-assertions": "error",
        "@typescript-eslint/dot-notation": "error",
        "@typescript-eslint/indent": [
          "error",
          2,
          {
            "FunctionDeclaration": {
              "parameters": "first"
            },
            "FunctionExpression": {
              "parameters": "first"
            },
            "SwitchCase": 1,
            "ignoredNodes": [
              "TSTypeParameterInstantiation"
            ]
          }
        ],
        "@typescript-eslint/member-delimiter-style": [
          "error",
          {
            "multiline": {
              "delimiter": "semi",
              "requireLast": true
            },
            "singleline": {
              "delimiter": "semi",
              "requireLast": false
            }
          }
        ],
        "@typescript-eslint/member-ordering": "error",
        "@typescript-eslint/no-empty-function": "off",
        "@typescript-eslint/no-empty-interface": "error",
        "@typescript-eslint/no-explicit-any": "off",
        "@typescript-eslint/no-inferrable-types": [
          "error",
          {
            "ignoreParameters": true
          }
        ],
        "@typescript-eslint/no-misused-new": "error",
        "@typescript-eslint/no-namespace": "error",
        "@typescript-eslint/no-non-null-assertion": "error",
        "@typescript-eslint/no-parameter-properties": "off",
        "no-unused-vars": "off",
        "@typescript-eslint/no-unused-vars": [
          "error"
        ],
        "no-shadow": "off",
        "@typescript-eslint/no-shadow": "error",
        "@typescript-eslint/no-unused-expressions": "error",
        "@typescript-eslint/no-use-before-define": "off",
        "@typescript-eslint/no-var-requires": "off",
        "@typescript-eslint/prefer-for-of": "error",
        "@typescript-eslint/prefer-function-type": "error",
        "@typescript-eslint/prefer-namespace-keyword": "error",
        "@typescript-eslint/semi": [
          "error",
          "always"
        ],
        "@typescript-eslint/triple-slash-reference": [
          "error",
          {
            "path": "always",
            "types": "prefer-import",
            "lib": "always"
          }
        ],
        "@typescript-eslint/type-annotation-spacing": "error",
        "@typescript-eslint/unified-signatures": "error",
        "@typescript-eslint/explicit-module-boundary-types": [
          "error",
          {
            "allowArgumentsExplicitlyTypedAsAny": true
          }
        ],
        "prefer-const": [
          "error",
          {
            "destructuring": "any",
            "ignoreReadBeforeAssign": true
          }
        ]
      }
    },
    {
      "files": [
        "*.html"
      ],
      "parser": "@angular-eslint/template-parser",
      "extends": [
        "plugin:@angular-eslint/template/recommended"
      ],
      "rules": {
        "@angular-eslint/template/banana-in-box": "error",
        "@angular-eslint/template/eqeqeq": "error",
        "@angular-eslint/template/no-negated-async": "error"
      }
    }
  ]
}

ESLint replaces TSLint and has the benefit that it can handle more than just your .ts files.

Bootstrap

Quickly design and customize responsive mobile-first sites with Bootstrap, the world’s most popular front-end open source toolkit, featuring Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful JavaScript plugins.

Of course this step is optional and you can use whatever you like. But I’m still a fan of bootstrap and use it for my projects.

Installation

npm install bootstrap --save
npm install jquery --save
npm install popper.js --save

To be able to use it you need to change your angular.json file section …architect.build.options from

{
  "styles": [
    "src/styles.scss"
  ],
  "scripts": []
}

to

{
  "styles": [
    "node_modules/bootstrap/dist/css/bootstrap.min.css",
    "src/styles.scss"
  ],
  "scripts": [
    "node_modules/jquery/dist/jquery.min.js",
    "node_modules/bootstrap/dist/js/bootstrap.min.js"
  ]
}

There is also a section …architect.test.options but I haven’t figured out yet if it’s necessary to place the new lines there as well or not.

Font Awesome

Get vector icons and social logos on your website with Font Awesome, the web’s most popular icon set and toolkit.

Another optional step but I like the icons and they’re free =)

Installation

npm install @fortawesome/fontawesome-svg-core
npm install @fortawesome/free-solid-svg-icons
npm install @fortawesome/angular-fontawesome

With these in place open your app.module.ts and add the following import

import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';

Also don’t forget to add FontAwesomeModule into the import in the @NgModule in the same file.

Usage

With this in place you can import icons and then use them in your controllers and html. In your controller it looks like this

import { faCoffee } from "@fortawesome/free-solid-svg-icons";
...
export class HomeComponent {
  faCoffee = faCoffee;
}

And in html you can reference it like this

<fa-icon [icon]="faCoffee"></fa-icon>

Git Hooks

Git per default isn’t running any hooks locally but there is an option to set a folder for such scripts. To enable this feature and set a folder you can use a script in your package.json:

"postinstall": "git config core.hooksPath ./.githooks"

This command sets the local .githooks folder to be used for scripts when npm install is called. There is quite a list of possible scripts that can bee hooked. Here a list I found in the internet. If you need further details please just check the internet.

  • applypatch-msg
  • pre-applypatch
  • post-applypatch
  • pre-commit
  • prepare-commit-msg
  • commit-msg
  • post-commit
  • pre-rebase
  • post-checkout
  • post-merge
  • pre-receive
  • update
  • post-receive
  • post-update
  • pre-auto-gc
  • post-rewrite
  • pre-push

pre-commit

It turned out to be quite useful to run prettier and lint before a commit. For this adding a file .githooks/pre-commit (I think it needs to be set executable) can be added with this simple content:

#!/bin/sh
npm run prettier --stages && npm run lint || exit 1

This will reformat all your code, add the changes to your commit and then lint your code. If one of the steps fails your commit fails as well. Sure your commit takes a while with this and for bigger projects this isn’t really an option. But for smaller projects it was quite handy.

commit-msg

Also enforcing a proper commit message structure before a commit can be made turned out to be a time safer compared to server side checks that would make you rewrite messages before you can push them. To easily validate your message you can use this script:

#!/usr/bin/env bash

# regex to validate in commit msg
commit_regex='(#[0-9]+|merge) '
error_msg="Aborting commit. Your commit message needs to conform to $commit_regex"

if ! grep -iqE "$commit_regex" "$1"; then
    echo "$error_msg" >&2
    exit 1
fi

This will enforce your commit message to include either an issue number like #42 or the text merge which is quite handy for gitlab.