This is the web frontend of KoppaDb, an easy-to-use database management system that I (@Dassderdie) co-founded in 2020. The code was written between February 2018 and September 2021, and it is now publicly archived for portfolio purposes. The company was liquidated just a bit more than one year after its founding because we never managed to scale above our initial customer. However, it was a valuable experience, and I learned more about software development in this project than in the first two years of my bachelor's degree.
If you're looking for a high-level understanding of this codebase, you're in the right place. This section will provide you with the highlights.
- AuthenticationModal which is enforced via a RouteGuard
- Also includes pages for email verification, password reset and email change
- Responsive breadcrumb navigation (BreadcrumbService, BreadcrumbComponent)
- Configuration is saved in the RoutingModules
- A visual editor to create, edit and delete tables of the underlying relational database
- Click here for the code
Click on a table to edit its attributes
There are a lot of (attribute specific) options and validators
Foreign attributes can be used to model relations between tables
A foreign attribute can also have additional attributes (including further foreign attributes -> recursion!)
Most of the formulars are not only aware of their current validation status, but also of wether their value has been changed. If the user closes the tab or navigates away from the current page and has unsaved changes a prompt plops up. Click here for the code
Table entries can be created, edited and deleted
All versions of an entry are preserved and changes between them are highlighted in its history EntryHistoryComponent (also recursive for intermediate-foreign-relations)
- No-Code Filter creation
- Filters and displayed attributes are synchronized with the URL-Query-Parameters. This enables easy sharing via a link and using bookmarks for saving them. EntrySearchComponent
- Integrated QR-Code scanner to link physical entities with database entries
- The scan algorithm itself runs in a WebWorker to guarantee a smooth camera live thread
- I18n via
ngx-translate
for the complete UI - TODO: Visualization of complex relations between entries
- A custom SharedWorker-based cache enables state consistency between tabs as well as automatic invalidation and refrehsing of all data (CacheServer code).
Click me to see parts of the original README.
For most this is a typical angular-app:
in /app
and every descending folder/module
/core
:- singleton-services with a state etc. that can be used by all other components/services/pipes... that are direct or indirect children of the
core
's parent-folder
- singleton-services with a state etc. that can be used by all other components/services/pipes... that are direct or indirect children of the
/shared
:- components, pipes, directives, classes, functions, types etc. that (can) have multiple instances and can be used by all other components/services/pipes... that are direct or indirect children of the
shared
's parent-folder - to keep the size of the main-bundle small, everything (belonging in the
shared-folder
) that is used in main (app-module...) is located in themain-shared
-folder. Nothing that is in main should import sth. directly from the@shared
-folder - some stuff is reexported by
@shared
like e.g.@main-shared/pipes
, because e.g.PipesModule
should also include the pipes fromMainPipesModule
. - every folder in a
shared
-folder should have it's own module and should export sth. useful
- components, pipes, directives, classes, functions, types etc. that (can) have multiple instances and can be used by all other components/services/pipes... that are direct or indirect children of the
/feature
- here are components/pipes/directives that should only be used in the
/pages
-folder at the same level
exceptions: - the top
/feature
folder should only be used inapp.component.ts
- the
/shared/feature
folder should contains components that are used in multiple modules but are very specialized and will not be used on any other places
- here are components/pipes/directives that should only be used in the
/pages
:- in here all all modules/components/services etc. located for the routes (-> lazy loading)
- lazy-loaded-pages
- if there is a lazy loaded module via the router the corresponding folder with this module and all it's declarations is often at the same level as
/pages
,shared
,core
, etc. (e.g.app/pages/projects/tables
). With this design the module is still at the same level as the routing says without nesting everything to much.
- if there is a lazy loaded module via the router the corresponding folder with this module and all it's declarations is often at the same level as
Between the server and the client is the CacheServer. It sends and receives requests and caches them to reduce the server load. In the best case it is shared between all tabs through a SharedWorker. In case the browser doesn't support it, each tab creates a fallback on its main thread.
Run npm i
to install dependencies
-
npm run build
builds the frontend for the cli. The build also works as a good demonstration of our capabilities. -
npm run build-official
builds our official cloud based website in production. The build artifacts will be stored in thedist/
directory. -
npm run build-customer-on-premise
builds the website for onPremise use for a customer. -
npm run build-customer-online
builds the website for a customer when it should be hosted on our own server open to the internet. -
Please provide for all
build-customer-…
the correct config insrc/environments/customer/config.json
(use theconfig.example.json
as an example)- you can change the config easily after you already build the frontend by replacing the file in
dist/environments/customer/config.json
- you can change the config easily after you already build the frontend by replacing the file in
-
Copy
src/environments/environments/development.example.env.ts
and rename it todevelopment.env.ts
. You can change all the environment variables to your liking. -
Copy
src/proxy.conf.json.example
, rename it tosrc/proxy.conf.json
and (if necessary) change the target to the url on which the db-backend is hosted. (See here for help) -
For most development you can use
npm run serve
. A live server will be reachable underhttp://localhost:4200
. The page will automatically reload after you make changes in the sourcecode. You don't have to mark the Update on reload-option in the browser. - It should automatically update and takeover the client. For some special cases it could still be practical (clearing cache even if service-worker didn't change). -
With
npm run start
the project will be build in development-mode, with an active service-worker (if you enable it in the environment too), but no live-reload. The build artifacts will be stored in thedist/
directory. -
When not building in production a CSP is set via the meta-attribute in the html-header. This is to prevent unpleasant surprises after the deployment with a CSP in the https-header.
The frontend is internationalized. In the source-code are therefore translation-keys like _('key.to.translation')
. With npm run extract-i18n
the json-files under src/assets/i18n
will be automatically updated (See here). Don't forget to insert the translations afterwards.
Run npm run lint
or npm run lint:fix
to automatically format and lint the whole project.
example tasks.json
:
{
"version": "2.0.0",
"tasks": [
{
"label": "Serve frontend official",
"type": "shell",
// use the commands specified in `package.json` as reference
"command": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng serve",
"isBackground": true,
"presentation": {
"focus": true,
"clear": true,
"panel": "dedicated"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": {
"owner": "typescript",
"source": "ts",
"applyTo": "closedDocuments",
"fileLocation": ["relative", "${cwd}"],
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "Compiled |Failed to compile."
}
}
}
}
]
}
and launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "Serve frontend",
"url": "http://localhost:4200/",
// Set to a random port that is still free
"port": 4100,
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
}
}
]
}
(For additional information see here)
- Angular Language Service https://github.com/angular/vscode-ng-language-service
- Angular Schematics
- Debugger for Chrome
Because we use $
in variable names it is encouraged to remove $
from the Word separators
in the editor. Else the whole variable is not included in the default selection.
Run ng generate component component-name
to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module
.
(For everyone preferring a gui: Angular Schematics (vscode))
ng-run is great for testing/reproducing angular/rxjs related problems (make sure that ts-strict-mode and Ivy-AOT are activated as well as all dependencies up to date).
For analyzing the builded bundle run npm run analyse-bundle
.
To run the tests Chrome has to be installed.
Run ng test
to execute the unit tests via Karma.
We currently do not have any end-to-end tests.
- Update the version in
package.json
- Add the version with the appropriate metadata to the changelog-array under
src/app/shared/display-changelog/changelog.ts
- (Optional) add html-files with release-notes to the assets (more information in the
changelog.ts
)
We are mostly relying on eslint and prettier for enforcing code style. Nevertheless there are some additional conventions:
- Variables for injected services should have the same name as the injected service
class AComponent {
constructor(
private readonly serviceWorkerService: ServiceWorkerService,
private readonly authService: AuthService,
i18nService: I18nService
) {}
}
- Name Observables with the postfix
$
(shortcut forStream
); it is allowed to not use this postfix if it doesn't improve the code -> no finnish-notation rule is enforced
const item: Item;
const items: Item[];
const item$: Observable<Item>;
const items$: Observable<Item[]>;
const item$s: Observable<Item>[];
- Use the postfix
E$
(EmitterStream
) for Subjects that should not be exposed
class AService {
private readonly itemE$ = new ReplaySubject<Item>(1);
public readonly item$ = this.itemE$.asObservable();
}
- The html of a route-component should most of the times manually be wrapped in a
<div class="page-container"></div>
. This makes it possible to make specific exceptions for e.g. a very wide table in contrast to setting thecontainer
class on the first level. Be aware that<router-outlet></router-outlet>
should never be placed in a container. - There is a
styles-variables.ts
for CSS-values that have to be used in the JS part too. - All Components use
ChangeDetectionStrategy.OnPush
- be aware that this ChangeDetectionStrategy gets inherited by child components. So you must programm for it too.
To get more help on the Angular CLI use ng help
or go check out the Angular CLI README.