Rockin' The Angular World!

By Vincent OGLOBLINSKY & Wassim CHEGHAM

How To Be Successful With Your Next Angular Projects.

 

This talk is based on our own personal experience.

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

DISCLAIMER

Vincent OGLOBLINSKY

Web Architect at SII

Wassim

CHEGHAM

GDE Web

ngx.tools, xlayers.dev, hexa.run

Sr. Cloud Advocate at MICROSOFT

GDE Web

Author of Compodoc

@vogloblinsky

@manekinekko

Angular 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

 

Business logic.

Business logic only!

 

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

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.

slido.com

#A034

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 (demo!).

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.

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 v9.

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!

« "If you're having trouble succeeding, fail »

- Kent Beck

Thanks!

@vogloblinsky

@manekinekko

Vincent OGLOBLINSKY

Wassim CHEGHAM

Rockin' the Angular World

By Wassim Chegham

Rockin' the Angular World

« Make it work, make it right, make it fast » - Kent Beck We all know this quote. But we sometimes stop at the first stage, lacking time or prioritization. Onboarding a new developer in a team is the best time to check if your codebase is « scalable and healthy ». We will explain in this talk some feedbacks from many years doing consulting on Angular projects with concrete use-cases : - what are the bad practices to avoid? - what are the opinionated choices of Angular which can help you? - regardless of the size of your team or your colleagues' experience, what are the best architectural choices you can do to enhance that?

  • 4,605