Saturday, May 31, 2025

Interview Limix (frontend dev)

The Thailand based start-up named Limix is very young (less than one year old!) and somehow I got an interview invitation from a Vietnamese head hunter. She had a short call to introduce the company, its benefits and the interview process. (they provided a 3-star hotel appartment for their employees, impressive!) 

There was some uncertainties about this company and I am a bit worried but I decided to take the chance anyways.

The interviewers were 2 guys, but they did not show their webcam, only me was the one showing face (to prevent cheating, you know, now AI is everywhere and it’s really intelligent). 


They asked me about my work, what are the challenges and how did I overcome them. Also the questions focused on SEO and web optimization (I don’t have much experience on these fields anyway). 


Lastly they asked if I am willing to switch to Vue because they are using this framework, and gave me a home test (using Vue of course). The test was about cloning  bitis.com.vn site using nuxt3 and vue3, with lighthouse score to be 100. I had one week to complete the test.


I tried my best to finish (worked my ass off really!). But I did not pass the test eventually.

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