10 Tips I Wish I Knew When I Started Using Angular

By Wassim CHEGHAM

How To Be Successful With Your Next Angular Projects.

 

This talk is based on my own experience.

YOURS MIGHT BE DIFFERENT! let's chat...

DISCLAIMER

Microsoft

Sr. Developer Advocate (JavaScript)

WassimChegham

https://wassim.dev

Open Source creator

More at wassim.dev

Community Contrib.

Angular GDE (core team alumni.)

Bazel Web Team contributor

GDE for the Google Assistant

Angular Universal (original core team alumni.)

GDE for GCP (alumni.)

Auth0 Ambassador

Member of the Node.js org. (OpenJS Foundation)

NestJS contributor

Compodoc core team

« Make it work,

make it right,

make it fast (if needed) »

- Kent Beck

Common bad practices to avoid.

Do it the Angular way!

Common successful Patterns.

We will cover...

(plus few anecdotes)

It's about Code & People.

Let's talk about Code...

Angular Modules ≠ ES Modules

import foo from 'bar';
@NgModule({...})
export class AppModule {}

Angular Modules convention.

Core

Shared

Features

Project's internal APIs.

Interceptors, ExceptionHandler...

 

No business logic!

Local & global Shared APIs

 

Possibly business logic.

Modules, Components, Services, Pipes, Directives...

 

Business logic only!

Angular Shared Modules.

AppModule

SalesModule

ContactModule

CartModule

...

Global SharedModule

FormsModule

BrowserModule

RouterModule

SharedModule

SharedModule

SharedModule

...

Fat Ugly Global Shared Modules.

AppModule

SalesModule

ContactModule

CartModule

...

Global SharedModule

Modules, Providers, Components, Directives, Pipes, etc.

FormsModule

BrowserModule

RouterModule

Red flag.

Avoid cluttering the global Shared Module. Use local shared modules.

Tip 00.

Avoid putting Providers in the global Shared Module (Providers corruption!)

Tip 01.

NgModules are going to be optional in the future.

Tip 02.

Lazy Loaded Modules.

export const routes: Route[] = [
  {
    path: '',
    redirectTo: '/home',
    pathMatch: 'full'
  },
  {
    path: 'home',
    loadChildren: () => import('../home/home.module')
                           .then(e => e.HomeModule)
  },
  {
    path: 'editor',
    loadChildren: () => import('./editor/editor.module')
                            .then(e => e.EditorModule)
  },
  {
    path: 'upload',
    loadChildren: () => import('./upload/upload.module')
                            .then(e => e.UploadModule)
  },
  {
    path: '**',
    redirectTo: '/home'
  }
];
export const routes: Route[] = [
  {
    path: '',
    redirectTo: '/home',
    pathMatch: 'full'
  },
  {
    path: 'home',
    component: HomeComponent
  },
  {
    path: 'editor',
    component: EditorComponent
  },
  {
    path: 'upload',
    component: UploadComponent
  },
  {
    path: '**',
    redirectTo: '/home'
  }
];

Angular Folder Structure.

.
├── BUILD.bazel
├── app-routing.module.ts
├── app.component.html
├── app.component.ts
├── app.module.ts
├── billing
│   ├── BUILD.bazel
│   ├── billing.module.ts
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp0
│   │   │   ├── cmp0.component.html
│   │   │   ├── cmp0.component.scss
│   │   │   ├── cmp0.component.spec.ts
│   │   │   └── cmp0.component.ts
│   │   ├── cmp1
│   │   │   ├── cmp1.component.html
│   │   │   ├── cmp1.component.scss
│   │   │   ├── cmp1.component.spec.ts
│   │   │   └── cmp1.component.ts
│   │   └── module0.module.ts
│   └── module1
│       ├── BUILD.bazel
│       ├── cmp2
│       │   ├── cmp2.component.html
│       │   ├── cmp2.component.scss
│       │   ├── cmp2.component.spec.ts
│       │   └── cmp2.component.ts
│       ├── cmp3
│       │   ├── cmp3.component.html
│       │   ├── cmp3.component.scss
│       │   ├── cmp3.component.spec.ts
│       │   └── cmp3.component.ts
│       └── module1.module.ts
├── compute
│   ├── BUILD.bazel
│   ├── compute.module.ts
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp4
│   │   │   ├── cmp4.component.html
│   │   │   ├── cmp4.component.scss
│   │   │   ├── cmp4.component.spec.ts
│   │   │   └── cmp4.component.ts
│   │   ├── cmp5
│   │   │   ├── cmp5.component.html
│   │   │   ├── cmp5.component.scss
│   │   │   ├── cmp5.component.spec.ts
│   │   │   └── cmp5.component.ts
│   │   └── module0.module.ts
│   └── module1
│       ├── BUILD.bazel
│       ├── cmp6
│       │   ├── cmp6.component.html
│       │   ├── cmp6.component.scss
│       │   ├── cmp6.component.spec.ts
│       │   └── cmp6.component.ts
│       ├── cmp7
│       │   ├── cmp7.component.html
│       │   ├── cmp7.component.scss
│       │   ├── cmp7.component.spec.ts
│       │   └── cmp7.component.ts
│       └── module1.module.ts
├── datastore
│   ├── BUILD.bazel
│   ├── datastore.module.ts
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp8
│   │   │   ├── cmp8.component.html
│   │   │   ├── cmp8.component.scss
│   │   │   ├── cmp8.component.spec.ts
│   │   │   └── cmp8.component.ts
│   │   ├── cmp9
│   │   │   ├── cmp9.component.html
│   │   │   ├── cmp9.component.scss
│   │   │   ├── cmp9.component.spec.ts
│   │   │   └── cmp9.component.ts
│   │   └── module0.module.ts
│   └── module1
│       ├── BUILD.bazel
│       ├── cmp10
│       │   ├── cmp10.component.html
│       │   ├── cmp10.component.scss
│       │   ├── cmp10.component.spec.ts
│       │   └── cmp10.component.ts
│       ├── cmp11
│       │   ├── cmp11.component.html
│       │   ├── cmp11.component.scss
│       │   ├── cmp11.component.spec.ts
│       │   └── cmp11.component.ts
│       └── module1.module.ts
├── functions
│   ├── BUILD.bazel
│   ├── functions.module.ts
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp12
│   │   │   ├── cmp12.component.html
│   │   │   ├── cmp12.component.scss
│   │   │   ├── cmp12.component.spec.ts
│   │   │   └── cmp12.component.ts
│   │   ├── cmp13
│   │   │   ├── cmp13.component.html
│   │   │   ├── cmp13.component.scss
│   │   │   ├── cmp13.component.spec.ts
│   │   │   └── cmp13.component.ts
│   │   └── module0.module.ts
│   └── module1
│       ├── BUILD.bazel
│       ├── cmp14
│       │   ├── cmp14.component.html
│       │   ├── cmp14.component.scss
│       │   ├── cmp14.component.spec.ts
│       │   └── cmp14.component.ts
│       ├── cmp15
│       │   ├── cmp15.component.html
│       │   ├── cmp15.component.scss
│       │   ├── cmp15.component.spec.ts
│       │   └── cmp15.component.ts
│       └── module1.module.ts
├── hello-world
│   ├── BUILD.bazel
│   ├── hello-world.component.html
│   ├── hello-world.component.scss
│   ├── hello-world.component.spec.ts
│   ├── hello-world.component.ts
│   └── hello-world.module.ts
├── home
│   ├── BUILD.bazel
│   ├── home.html
│   └── home.ts
├── logging
│   ├── BUILD.bazel
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── logging.module.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp16
│   │   │   ├── cmp16.component.html
│   │   │   ├── cmp16.component.scss
│   │   │   ├── cmp16.component.spec.ts
│   │   │   └── cmp16.component.ts
│   │   ├── cmp17
│   │   │   ├── cmp17.component.html
│   │   │   ├── cmp17.component.scss
│   │   │   ├── cmp17.component.spec.ts
│   │   │   └── cmp17.component.ts
│   │   └── module0.module.ts
│   └── module1
│       ├── BUILD.bazel
│       ├── cmp18
│       │   ├── cmp18.component.html
│       │   ├── cmp18.component.scss
│       │   ├── cmp18.component.spec.ts
│       │   └── cmp18.component.ts
│       ├── cmp19
│       │   ├── cmp19.component.html
│       │   ├── cmp19.component.scss
│       │   ├── cmp19.component.spec.ts
│       │   └── cmp19.component.ts
│       └── module1.module.ts
├── monitoring
│   ├── BUILD.bazel
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp20
│   │   │   ├── cmp20.component.html
│   │   │   ├── cmp20.component.scss
│   │   │   ├── cmp20.component.spec.ts
│   │   │   └── cmp20.component.ts
│   │   ├── cmp21
│   │   │   ├── cmp21.component.html
│   │   │   ├── cmp21.component.scss
│   │   │   ├── cmp21.component.spec.ts
│   │   │   └── cmp21.component.ts
│   │   └── module0.module.ts
│   ├── module1
│   │   ├── BUILD.bazel
│   │   ├── cmp22
│   │   │   ├── cmp22.component.html
│   │   │   ├── cmp22.component.scss
│   │   │   ├── cmp22.component.spec.ts
│   │   │   └── cmp22.component.ts
│   │   ├── cmp23
│   │   │   ├── cmp23.component.html
│   │   │   ├── cmp23.component.scss
│   │   │   ├── cmp23.component.spec.ts
│   │   │   └── cmp23.component.ts
│   │   └── module1.module.ts
│   └── monitoring.module.ts
├── networking
│   ├── BUILD.bazel
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp24
│   │   │   ├── cmp24.component.html
│   │   │   ├── cmp24.component.scss
│   │   │   ├── cmp24.component.spec.ts
│   │   │   └── cmp24.component.ts
│   │   ├── cmp25
│   │   │   ├── cmp25.component.html
│   │   │   ├── cmp25.component.scss
│   │   │   ├── cmp25.component.spec.ts
│   │   │   └── cmp25.component.ts
│   │   └── module0.module.ts
│   ├── module1
│   │   ├── BUILD.bazel
│   │   ├── cmp26
│   │   │   ├── cmp26.component.html
│   │   │   ├── cmp26.component.scss
│   │   │   ├── cmp26.component.spec.ts
│   │   │   └── cmp26.component.ts
│   │   ├── cmp27
│   │   │   ├── cmp27.component.html
│   │   │   ├── cmp27.component.scss
│   │   │   ├── cmp27.component.spec.ts
│   │   │   └── cmp27.component.ts
│   │   └── module1.module.ts
│   └── networking.module.ts
├── registry
│   ├── BUILD.bazel
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp28
│   │   │   ├── cmp28.component.html
│   │   │   ├── cmp28.component.scss
│   │   │   ├── cmp28.component.spec.ts
│   │   │   └── cmp28.component.ts
│   │   ├── cmp29
│   │   │   ├── cmp29.component.html
│   │   │   ├── cmp29.component.scss
│   │   │   ├── cmp29.component.spec.ts
│   │   │   └── cmp29.component.ts
│   │   └── module0.module.ts
│   ├── module1
│   │   ├── BUILD.bazel
│   │   ├── cmp30
│   │   │   ├── cmp30.component.html
│   │   │   ├── cmp30.component.scss
│   │   │   ├── cmp30.component.spec.ts
│   │   │   └── cmp30.component.ts
│   │   ├── cmp31
│   │   │   ├── cmp31.component.html
│   │   │   ├── cmp31.component.scss
│   │   │   ├── cmp31.component.spec.ts
│   │   │   └── cmp31.component.ts
│   │   └── module1.module.ts
│   └── registry.module.ts
├── storage
│   ├── BUILD.bazel
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp32
│   │   │   ├── cmp32.component.html
│   │   │   ├── cmp32.component.scss
│   │   │   ├── cmp32.component.spec.ts
│   │   │   └── cmp32.component.ts
│   │   ├── cmp33
│   │   │   ├── cmp33.component.html
│   │   │   ├── cmp33.component.scss
│   │   │   ├── cmp33.component.spec.ts
│   │   │   └── cmp33.component.ts
│   │   └── module0.module.ts
│   ├── module1
│   │   ├── BUILD.bazel
│   │   ├── cmp34
│   │   │   ├── cmp34.component.html
│   │   │   ├── cmp34.component.scss
│   │   │   ├── cmp34.component.spec.ts
│   │   │   └── cmp34.component.ts
│   │   ├── cmp35
│   │   │   ├── cmp35.component.html
│   │   │   ├── cmp35.component.scss
│   │   │   ├── cmp35.component.spec.ts
│   │   │   └── cmp35.component.ts
│   │   └── module1.module.ts
│   └── storage.module.ts
├── support
│   ├── BUILD.bazel
│   ├── index
│   │   ├── index.component.html
│   │   ├── index.component.spec.ts
│   │   └── index.component.ts
│   ├── module0
│   │   ├── BUILD.bazel
│   │   ├── cmp36
│   │   │   ├── cmp36.component.html
│   │   │   ├── cmp36.component.scss
│   │   │   ├── cmp36.component.spec.ts
│   │   │   └── cmp36.component.ts
│   │   ├── cmp37
│   │   │   ├── cmp37.component.html
│   │   │   ├── cmp37.component.scss
│   │   │   ├── cmp37.component.spec.ts
│   │   │   └── cmp37.component.ts
│   │   └── module0.module.ts
│   ├── module1
│   │   ├── BUILD.bazel
│   │   ├── cmp38
│   │   │   ├── cmp38.component.html
│   │   │   ├── cmp38.component.scss
│   │   │   ├── cmp38.component.spec.ts
│   │   │   └── cmp38.component.ts
│   │   ├── cmp39
│   │   │   ├── cmp39.component.html
│   │   │   ├── cmp39.component.scss
│   │   │   ├── cmp39.component.spec.ts
│   │   │   └── cmp39.component.ts
│   │   └── module1.module.ts
│   └── support.module.ts
└── todos
    ├── BUILD.bazel
    ├── reducers
    │   ├── BUILD.bazel
    │   └── reducers.ts
    ├── todos.component.html
    ├── todos.component.scss
    ├── todos.component.ts
    └── todos.module.ts

84 directories, 271 files

Angular libraries.

$ ng generate library my-awesome-logger

Manual scripting.

➜  scripts git:(master) / tree
.
├── clean-changelog.js
├── cloudbuild
│   ├── deploy.sh
│   ├── kubectl
│   │   ├── Dockerfile
│   │   ├── cloudbuild.yaml
│   │   ├── kubectl.bash
│   │   └── publish.sh
│   ├── ngcontainer
│   │   ├── Dockerfile
│   │   ├── cloudbuild.yaml
│   │   ├── nginx.conf
│   │   └── publish.sh
│   └── xlayers.template.yaml
├── github-create-comment.bash
├── local-build.sh
└── stamp-build.bash
$ ng add @company/deploy

TypeScript "path alias".

import { ApiService } from '../../../core/api.service.ts';
import { CatsService } from '../../../services/cats.service.ts';
// tsconfig.json
{
  "CompilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@core/*": ["app/core/*"]
      "@services/*": ["app/services/*"]
    }
  }
}
import { ApiService } from '@core/api.service.ts';
import { CatsService } from '@services/cats.service.ts';

You should start investing in Schematics and Builders.

Tip 03.

JIT vs AOT.

Components

Code Generation

Compiler *

(Parser, Lexer, AST)

VM Code

(Renderer *)

JIT Compilation

(run time)

AOT Compilation (build time)

* In v8: ViewEngine. In v9+: Ivy

Build in Prod mode (AOT) as often as possible.

Tip 04.

Why is my build process so slow?

HTMLTS CSS

JavaScript

ng build

How ng build --prod works?

Architect.ScheduleTarget()

ng build --prod

angular.json: defaultProject

angular.json: architect.build.builder

(e.g. @angular-devkit/build-angular:browser)

Create a Webpack plugin:

Common, Browser, Stats, Styles, Worker, Analytics, AOT or JIT.

browserBuild()

AOT && buildOptimizerLoader()

Architect.run()

 

new AngularCompilerPlugin() (@ngtools/webpack)

 

ngc (ViewEngine)
ngcc (Ivy)

 

Consider keeping up to date with the latest releases:

ng update [--next]

Tip 05.

You should start investing in Bazel (slides!).

Tip 06.

Runtime performance checkup.

$ npm i webpack-bundle-analyzer -D

$ ng build --stats-json

$ webpack-bundle-analyzer dist/MyBundle/stats.json

Runtime performance checkup.

Setup a bundles analyzer as part of your CI/CD.

Tip 07.

Design systems...

Choose one!

& the one that matches best your needs

You should consider the Component DevKit.

Tip 08.

Testing, what and how.

Test only the business logic.

Think also about edge cases (e.g. NaN/0).

Separate your code for easier testing.

Smoke vs. Black Box Testing.

Don't let be driven by "indicators" : code coverage, etc.

→ Smoke testing

Understand very well Angular internals

→ Black box testing

Avoid importing Fat Shared Modules in the TestBed Module!

Tip 09.

Bonus...

DRY/SRP/SOLID principals.

Think in terms of responsibility (SRP).

Think about product features.

Organize with separation in mind.

DRY/SRP/SOLID principals.

Smart / Dumb components (DRY).

Write code that's easy to change, maintain, test.

Things should be removed quickly/easily.

DRY/SRP/SOLID principals.

Let's talk about People...

Reactive Programing Thinking.

this.entries$ = this.queries$.pipe(
  map((query: string) => query ? query.trim() : ''),
  filter(Boolean),
  debounceTime(500),
  distinctUntilChanged(),
  switchMap((query: string) => this.fetchEntries(query)),
  filterByOwnerType(OwnerType.User)
);

this.organizations$ = this.selectedRepository$.pipe(
  map((repository) => repository && repository.owner.organizations_url),
  switchMap((url: string | false) => {
    return url ? this.fetchUserOrganizations(url) : of(undefined);
  }),
);

Embrace Reactive Functional Programming.

Tip 10.

Don't let one person HOLDS all the knowledge.

Tip 11.

Comments and Documentation.

Think about you or a colleague 6 months later

Comment / document only what's needed

Let the type system do the job for you

Document the "why" and not only the "what"

Do you really need to upgrade?

Is your AngularJS application fine?

Are you adding new features to legacy?

Starting a new app? Use Angular LTS.

Remember...

Don’t let the tech drives your decisions.

"Common Sense" driven development.

Follow SRP/DRY/SOLID principles.

Don’t mix business code with UI/FW logics.

And have fun!!

Tips recap.

00. Avoid cluttering the global Shared Module. Use local shared modules.

02. NgModules are going to be optional in the future.

01. Avoid putting Providers in the global Shared Module.

03. You should start investing in Schematics and Builders.

04. Build in Prod mode as often as possible.

05. Consider keeping up to date with the latest releases.

06. You should start investing in Bazel.

07. Setup the bundles analyzer as part of your CI/CD.

08. You should consider the Component DevKit.

10. Embrace Reactive Functional Programming.

11. Don't let one person HOLDS all the knowledge.

09. Avoid importing Fat Shared Modules in the TestBed Module!

Thanks!

@manekinekko

Wassim CHEGHAM