Saturday, March 15, 2025

How to setup a monorepo app (Angular)

 Tech stacks:

  1. Angular 15+ (main framework)
  2. Nx (monorepo)
  3. ngRx (state management)
  4. cypress (e2e testing)
  5. jest (unit testing)
  6. prettier (code formatter)
  7. eslint (static code analysis)
  8. husky (git commit hook)
  9. SonarQube (code quality analysis)
  10. gitlab (CICD)
  11. strapi (CMS)
  12. lit element (UI web components)
  13. Kong (Gateway)
Workflow:
  1. Create new components with nx cli, or modify existing code (add tests if needed)
  2. Run unit test with jest, and e2e test with cypress
  3. Run sonar qube to check code quality
  4. Format the changes with prettier
  5. Commit code into appropriate feature branch (this will also trigger husky to check code format and eslint)
  6. See if the gitlab pipeline run success
  7. Create MR on gitlab
Configurations examples

1. Prettier

.prettierrc

{
"singleQuote": true,
"importOrderParserPlugins": ["typescript", "decorators-legacy"],
"importOrder": ["^@angular/(.*)$", "^@my-project/(.*)$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}

 2. Eslint

.eslintrc.json

{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nrwl/nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@nrwl/nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nrwl/nx/typescript"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nrwl/nx/javascript"],
"rules": {}
}
]
}

3. Jest

jest.config.ts


const { getJestProjects } = require('@nrwl/jest');

export default {
projects: [
...getJestProjects(),
'<rootDir>/apps/my-app',
'<rootDir>/libs/my-ui',
'<rootDir>/libs/helpers',
],
};

 4. SonarQube

sonar-project.properties

# Required metadata
sonar.projectKey=my.project
sonar.projectName=MY-PROJECT
sonar.projectVersion=$(date +%Y-%m-%d)
sonar.host.url=http://sonar.my.domain.com:8080

# Comma-separated paths to directories with sources (required)
sonar.sources=apps, libs
sonar.exclusions=**/*.spec.*, **/*.mock.*
# Language
sonar.language=ts

# Encoding of sources files
sonar.sourceEncoding=UTF-8
sonar.typescript.lcov.reportPaths=coverage/lcov.info
sonar.typescript.tsconfigPath=tsconfig.base.json

sonar.coverage.exclusions=**/*.spec.*
sonar.tests.inclusions=**/*.spec.*
sonar.ts.tslint.outputPath=tslint-output.json

sonar.qualitygate.wait=true

 5. TypeScript config

tsconfig.base.json

{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2017", "dom", "ES2018.Promise"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"noUncheckedIndexedAccess": true,
"paths": {
"@my-project/config": ["config"],
"@my-project/e2e-utils/*": [
"libs/e2e-utils/src/lib/*"
],
"@my-project/helpers": ["libs/helpers/src/index.ts"],
"@my-project/shared/ui": [
"libs/shared/ui/src/index.ts"
],
"@my-project/my-ui/*": ["libs/my-ui/src/lib/*"],
}
},
"exclude": ["node_modules", "tmp"]
}

6. Gitlab CICD

################################################### Workflow rules ###################################################
include:
- template: Workflow-rules.gitlab-ci.yml

image: $DOCKER_REGISTERY/my-image-store/nodejs:20.14.0

stages:
- install
- quality
- test
- build
- deploy

##########################[ install ]###################################
yarn:install:
stage: install
tags:
- cache_dev
variables:
GIT_DEPTH: 0
CYPRESS_INSTALL_BINARY: '0'
HUSKY_SKIP_INSTALL: '1'
script:
- |-
if [ ! -d "node_modules" ]; then
yarn config set registry http://repo.my.domain.com/repository/npm-group/ -g
yarn install --frozen-lockfile
else
echo "node_modules has been retrieved from cache. No need to launch the yarn install."
fi
cache:
- key:
files:
- yarn.lock
paths:
- node_modules/
artifacts:
paths:
- node_modules/
expire_in: 15 days

##########################[ quality ]###################################
yarn:lint:
stage: quality
tags:
- deploy_prod
needs:
- job: yarn:install
artifacts: true
before_script:
- '[ $CI_COMMIT_BRANCH = $CI_DEFAULT_BRANCH ] \
&& export BASE_COMPARE_BRANCH=remotes/origin/master~1 \
|| export BASE_COMPARE_BRANCH=remotes/origin/master'
- git fetch origin
script:
- yarn run nx -- affected --target=lint --base=$BASE_COMPARE_BRANCH --parallel
- yarn run nx -- format:check --parallel --verbose --base=$BASE_COMPARE_BRANCH
allow_failure: false
when: always

sonar:
image: $DOCKER_REGISTERY/sonarsource/sonar-scanner-cli:latest
stage: quality
tags:
- deploy_prod
- sonar
script:
- sonar-scanner -Dproject.settings=./sonar-project.properties
artifacts:
when: on_success
expire_in: 15 day
paths:
- .scannerwork/
rules:
- if: $CI_COMMIT_BRANCH == 'master'
when: always
allow_failure: true

##########################[ test ]###################################

yarn:unit-test:
extends: yarn:lint
stage: test
script: yarn run nx -- affected --target=test --base=$BASE_COMPARE_BRANCH --parallel
when: always
allow_failure: false

yarn:e2e-test-chrome:
extends: yarn:lint
stage: test
script:
- yarn run nx -- affected --target=e2e --base=origin/master --parallel
when: manual

##########################[ build ]###################################

b:dev:
stage: build
dependencies:
- yarn:install
tags:
- cache_dev
needs:
- job: yarn:install
artifacts: true
before_script:
- echo `date`
script:
- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=dev --parallel'
- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=dev --parallel'
after_script:
- echo `date`
- 'mkdir -p target'
- 'tar -zcf target/package.tar.gz dist'
- echo `date`
cache:
- key: nx-cache-$CI_COMMIT_BRANCH
paths:
- tmp/nx-cache
artifacts:
when: on_success
expire_in: 15 day
paths:
- target/
rules:
- when: manual

b:staging:
extends: b:dev
script:
- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=staging --parallel'
- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=staging --parallel'
allow_failure: false

b:preprod:
extends: b:dev
script:
- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=preproduction --parallel'
- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=preproduction --parallel'
allow_failure: false

b:prod:
extends: b:dev
script:
- 'yarn run nx -- run-many --target=build --projects=my-project,my-other-project --configuration=production --parallel'
- 'yarn run nx -- run-many --target=server --projects=my-project,my-other-project --configuration=production --parallel'
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: always
- when: never
- allow_failure: false

##########################[ deploy ]###################################

d:dev:
stage: deploy
variables:
GIT_STRATEGY: none
tags:
- deploy_dev
environment:
name: dev
url: https://my.domain.com:4004
needs:
- job: b:dev
artifacts: true
script:
- bash /tools/deploy-tool
rules:
- when: manual
allow_failure: true

d:staging:
extends: d:dev
environment:
name: staging
url: https://my.domain.com:4004
needs:
- job: b:staging
artifacts: true

d:preprod:
extends: d:dev
tags:
- deploy_prod
environment:
name: preprod
needs:
- job: b:preprod
artifacts: true

d:prod:
extends: d:dev
tags:
- deploy_prod
environment:
name: prod
needs:
- job: b:prod
artifacts: true
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
- when: never
- allow_failure: false

 7. Husky & other run scripts

{
"name": "my-project",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"start:my-project": "ng serve --project my-project",
"e2e:my-project": "nx e2e my-project-e2e",
"affected:build": "nx affected:build",
"affected:e2e": "nx affected:e2e",
"affected:test": "nx affected:test",
"affected:lint": "nx affected:lint --parallel --uncommitted",
"affected:dep-graph": "nx affected:dep-graph",
"affected": "nx affected",
"format": "nx format:write",
"format:check": "nx format:check --parallel --uncommitted",
"update": "nx migrate latest",
"update:check": "ng update",
"dep-graph": "nx dep-graph",

},
"husky": {
"hooks": {
"pre-commit": "yarn run affected:lint && yarn run format:check"
}
},
"private": true,
"dependencies": {
"@angular-devkit/core": "15.2.11",
"@angular-devkit/schematics": "15.2.11",
"@angular/animations": "15.2.10",
"@angular/cdk": "14.2.7",
"@angular/common": "15.2.10",
"@angular/compiler": "15.2.10",
"@angular/core": "15.2.10",
"@angular/forms": "15.2.10",
"@angular/material": "14.2.7",
"winston": "3.3.3",
"zone.js": "0.11.8"
},
"devDependencies": {
"@angular-devkit/build-angular": "15.2.11",
"@angular-eslint/eslint-plugin": "14.0.4",
"@angular-eslint/eslint-plugin-template": "14.0.4",
"@angular-eslint/template-parser": "14.0.4",
"@angular/cli": "~15.2.11",
"typescript": "4.8.4",
"webpack": "^5.58.1",
"yarn": "1.22.19"
}
}

8. Kong

proxy.conf.json

{
"/api": {
"target": "https://apim-kong-staging.dev.mydomain.com:8080/api-path",
"secure": false,
"logLevel": "debug",
"pathRewrite": {
"^/api": ""
},
"headers": {
"apiKey": "apikey..."
}
}
}

 

angular.json > projects > my-project > architect > serve

"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "my-project:build",
"host": "my.local.domain.com",
"proxyConfig": "proxy.conf.json"
},

 

Thursday, February 29, 2024

Interview questions for Angular dev

Basic FrontEnd

html

localstorage, sessionstorage, cookie

web components

shadow dom

accessibility

Which code sets the colspan attribute on the td element?

css/scss

box model

pseudo element

positions: static, absolute...

layouts: flex, grid

selectors: tag, id, class... specificity

scss: var, func, nesting

mobile first


js/ts

data types: number, string, null, undefined

ts type: unknown, any

scope: global/function/block

closure

prototype

array: map, filter, reduce, some, push, concat...

operators: ??, ||

arrow function vs. normal function, what's "this" value

async function: why js is single thread but can process async events


const obj = { a: "one", b: "two", a: "three" }; console.log(obj)

viết hàm async tính giai thừa của n (n >= 0)


Angular


Basic

how many years of exprience

Angular overall architecture

Angular directive (structive, attribute) 

directive vs. component

life cycle hooks

how 2 components communicate

syntax to lazy-load a module

UI library: primeNg, material

What is the syntax for a template reference variable?


Advanced

How a component get data from main app

change detection: onPush vs. Default

What is the default value of 'ViewEncapsulation'?

role of interceptor

type of valuechanges in formcontrol

selector used to style a component based on a condition outside of the view

when impure pipe run

How can you have a separate instance of service "MyMockService" in every instance of a component?

At what lifecycle hook will ViewChild query be accessible? @ViewChild(foo', (static: false}) foo: ElementRef

What is differential loading?

How can you specify an alternative class provider?

control value accessor

tại sao data trong 1 service không bị mất khi chuyển trang

làm thế nào để ng-content select đúng template

dùng gì để provide data cho một page trc khi chuyển đến

state management

ngrx

how it works

Example

Which syntax completes the following code so that it displays 

'Hello Angular!', given that isTrue = false; ?           <span>Hello

<span …›world</span></span><ng-template #hi>Angular</ng-template>


How many times will console.log be called in the following example? import {of} from 'rxjs';   of([2,3,41).subscribe({next: n => console.log(n)})

rxjs

các rxjs operator hay dùng

switchMap vs. exhaustMap, nếu có error thì có chạy tiếp k

forkJoin vs. combineLatest

Observable vs. promise

rxjs map vs. switchMap

Testing

unit test vs. e2e test

cypress, mocha...

TDD

clean code

SOLID

how to refactor code


design pattern

DI, Facade,...


code review

coding style

communicate BE vs FE


Debug 

- chrome dev tools

- angular dev tools

- redux


GIT

git flow

merge vs. rebase


BE

SQL schema

Restful (http verbs, put vs. patch)

PSR

Optimization

Debug

SQL index: btree, hash


others

 Describe your project architecture

Monday, February 5, 2024

First test in new year

 I've just taken a test provided by Hackerrank, to apply for a FE senior position in Positive Thinking Company.

The test contained 5 single-choice questions and 2 coding challenges.

I quickly moved over the first 5 questions, then stucked on the 6th one which is an algorithm coding exercise. 

The question asked to get the maximum value of ratings array if we cannot skip 2 concecutive numbers (can skip one then must take the next one). In this case we need to have 

Example:

input:

[-1, -2, -3, -4, -5]

output:

-6

explanation:

take [-2, -4] and skip others to have maximal number of -6

The last question was about moving data among parttitions in a hard disk to store data in the least partitions possible.

Example:

input:

- used: [3, 2, 1, 3, 1]

- total capacity: [3, 5, 3, 5, 5]

output:

2

explanation:

move first partition to second partition, move third and last partitions to 4th partition, then we have result of [0, 5, 0, 5, 0] which stores in 2 partitions.

I had feedback right away: FAILED :(

40% (need 50% to be mid and 60% to be senior)


I need to practice more algorithm then.