Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Business Architectures with Angular Libraries, NX and Monorepos

Business Architectures with Angular Libraries, NX and Monorepos

Angular is a great tool for creating applications with a big architecture in mind. It comes with benefits like testing, Dependency Injection or HTTP Communication out of the box. But when your application grows and often enlarges, a single Angular application comes unhandy and developer seem to lose the focus on where to put which feature. And after managing Angular's learning curve, designing shared features or loosely couples fragments while developing an app seems to be a challenge still because having everything in one app seems not to be a well-structured architecture.

In this talk Fabian Gosebrink will explain the power of Angular's architectural features and push them to the edge where the extraction of libraries and creating a complete workspace with tools like NX can save you and help you to write clean large Angular applications, lovely shareable features and gets your Angular business application back into shape again. Because large angular apps don't have to be difficult to maintain.

Fb89953d3a1b1fcb862a186585c37c25?s=128

Fabian Gosebrink

April 26, 2022
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. Business Architectures with Angular Libraries, NX and Monorepos

  2. Why?

  3. Encapsulation

  4. Angular Modules

  5. Feature Modules

  6. import { NgModule } from '@angular/core'; @NgModule({ imports: [ ...

    ], declarations: [ AppComponent, HelloComponent, Hello1Component, Hello2Component, Hello3Component, Hello4Component, Hello5Component, Hello6Component, Hello7Component, Hello8Component, Hello9Component, Hello10Component, Hello11Component, Hello12Component, Hello13Component, Hello14Component, Hello15Component 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  7. import { NgModule } from '@angular/core'; @NgModule({ imports: [ ...

    ], declarations: [ AppComponent, Feature1Component1, Feature1Component2, Feature1Component3, Feature1Component4, Feature1Component5, Feature1Component6, Feature2Component1, Feature2Component2, Feature2Component3, Feature2Component4, Feature2Component5, Feature2Component6, ... ], bootstrap: [AppComponent] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  8. import { NgModule } from '@angular/core'; import { CommonModule }

    from '@angular/common'; @NgModule({ imports: [ CommonModule ], declarations: [ Feature1Component1, Feature1Component2, Feature1Component3, Feature1Component4, Feature1Component5, Feature1Component6, ], exports: [...] }) export class Feature1Module { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  9. import { NgModule } from '@angular/core'; import { CommonModule }

    from '@angular/common'; @NgModule({ imports: [ CommonModule ], declarations: [ Feature2Component1, Feature2Component2, Feature2Component3, Feature2Component4, Feature2Component5, Feature2Component6, ], exports: [...] }) export class Feature2Module { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  10. import { NgModule } from '@angular/core'; @NgModule({ imports: [ ],

    declarations: [ AppComponent, Feature1Component1, Feature1Component2, Feature1Component3, Feature1Component4, Feature1Component5, Feature1Component6, Feature2Component1, Feature2Component2, Feature2Component3, Feature2Component4, Feature2Component5, Feature2Component6, ], }) export class AppModule { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  11. import { NgModule } from '@angular/core'; import { Feature1Module }

    from './feature1-module/feature1.module'; import { Feature2Module } from './feature2-module/feature2.module'; @NgModule({ imports: [ BrowserModule, FormsModule, Feature1Module, Feature2Module ], declarations: [ AppComponent, HelloComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  12. import { NgModule } from '@angular/core'; import { Feature1Module }

    from './feature1-module/feature1.module'; import { Feature2Module } from './feature2-module/feature2.module'; @NgModule({ imports: [ BrowserModule, FormsModule, Feature1Module, Feature2Module ], declarations: [ AppComponent, HelloComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { Feature1Module } from './feature1-module/feature1.module'; import { Feature2Module } from './feature2-module/feature2.module'; Feature1Module, Feature2Module import { NgModule } from '@angular/core'; 1 2 3 4 @NgModule({ 5 imports: [ 6 BrowserModule, 7 FormsModule, 8 9 10 ], 11 declarations: [ 12 AppComponent, 13 HelloComponent 14 ], 15 bootstrap: [ AppComponent ] 16 }) 17 export class AppModule { } 18
  13. Business Architectures with Angular Libraries, NX and Monorepos

  14. Libraries

  15. None
  16. NG Workspace

  17. NG Workspace

  18. NG Workspace

  19. NG Workspace

  20. None
  21. None
  22. None
  23. import { /* ... */ } from 'lib-to-configure'; import {

    NgModule, APP_INITIALIZER, Injectable } from '@angular/core'; 1 import { BrowserModule } from '@angular/platform-browser'; 2 3 4 5 import { AppComponent } from './app.component'; 6 7 export class AppModule {} 8
  24. import { /* ... */ } from 'lib-to-configure'; import {

    NgModule, APP_INITIALIZER, Injectable } from '@angular/core'; 1 import { BrowserModule } from '@angular/platform-browser'; 2 3 4 5 import { AppComponent } from './app.component'; 6 7 export class AppModule {} 8
  25. @NgModule({ declarations: [LibToConfigureComponent], imports: [CommonModule], exports: [LibToConfigureComponent], }) export class

    LibToConfigureModule {} 1 2 3 4 5 6
  26. @NgModule({ declarations: [LibToConfigureComponent], imports: [CommonModule], exports: [LibToConfigureComponent], }) export class

    LibToConfigureModule {} 1 2 3 4 5 6 exports: [LibToConfigureComponent], @NgModule({ 1 declarations: [LibToConfigureComponent], 2 imports: [CommonModule], 3 4 }) 5 export class LibToConfigureModule {} 6 @NgModule({ declarations: [AppComponent], imports: [BrowserModule, LibToConfigureModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {} 1 2 3 4 5 6 7
  27. @NgModule({ declarations: [LibToConfigureComponent], imports: [CommonModule], exports: [LibToConfigureComponent], }) export class

    LibToConfigureModule {} 1 2 3 4 5 6 exports: [LibToConfigureComponent], @NgModule({ 1 declarations: [LibToConfigureComponent], 2 imports: [CommonModule], 3 4 }) 5 export class LibToConfigureModule {} 6 @NgModule({ declarations: [AppComponent], imports: [BrowserModule, LibToConfigureModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {} 1 2 3 4 5 6 7 imports: [BrowserModule, LibToConfigureModule], @NgModule({ 1 declarations: [AppComponent], 2 3 providers: [], 4 bootstrap: [AppComponent], 5 }) 6 export class AppModule {} 7
  28. None
  29. None
  30. None
  31. What is

  32. Bring Order

  33. Have Flexibility

  34. Tooling

  35. Tooling

  36. NX Workspace

  37. None
  38. None
  39. None
  40. Rethinking Libraries

  41. Libraries Share code between apps

  42. Libraries Share code between apps Provide feature to one app

  43. Libraries Share code between apps Provide feature to one app

    Multiple Libs = 1 Feature
  44. Feature Feature Ui Data access Utils

  45. None
  46. None
  47. Folders

  48. Folders Lib: Api Lib: Data Lib: Ui Lib: Util Lib:

    Feature
  49. Folders Lib: Api Lib: Data Lib: Ui Lib: Util Lib:

    Feature
  50. https://go.nrwl.io/angular-enterprise-monorepo-patterns-new-book

  51. Feature Ui vs.

  52. Container Presentational vs.

  53. Stateful import { /* ... */ } from '...'; @Component({

    selector: 'workspace-groups-meetups-list', templateUrl: './groups-meetups-list.component.html', styleUrls: ['./groups-meetups-list.component.scss'], }) export class GroupsMeetupsListComponent implements OnInit { items$: Observable<Meetup[]>; loading$: Observable<boolean>; constructor(private store: Store) {} ngOnInit() { this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  54. Stateful import { /* ... */ } from '...'; @Component({

    selector: 'workspace-groups-meetups-list', templateUrl: './groups-meetups-list.component.html', styleUrls: ['./groups-meetups-list.component.scss'], }) export class GroupsMeetupsListComponent implements OnInit { items$: Observable<Meetup[]>; loading$: Observable<boolean>; constructor(private store: Store) {} ngOnInit() { this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 items$: Observable<Meetup[]>; loading$: Observable<boolean>; import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 9 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21
  55. Stateful import { /* ... */ } from '...'; @Component({

    selector: 'workspace-groups-meetups-list', templateUrl: './groups-meetups-list.component.html', styleUrls: ['./groups-meetups-list.component.scss'], }) export class GroupsMeetupsListComponent implements OnInit { items$: Observable<Meetup[]>; loading$: Observable<boolean>; constructor(private store: Store) {} ngOnInit() { this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 items$: Observable<Meetup[]>; loading$: Observable<boolean>; import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 9 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 constructor(private store: Store) {} import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21
  56. Stateful import { /* ... */ } from '...'; @Component({

    selector: 'workspace-groups-meetups-list', templateUrl: './groups-meetups-list.component.html', styleUrls: ['./groups-meetups-list.component.scss'], }) export class GroupsMeetupsListComponent implements OnInit { items$: Observable<Meetup[]>; loading$: Observable<boolean>; constructor(private store: Store) {} ngOnInit() { this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 items$: Observable<Meetup[]>; loading$: Observable<boolean>; import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 9 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 constructor(private store: Store) {} import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 15 16 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21
  57. Stateful import { /* ... */ } from '...'; @Component({

    selector: 'workspace-groups-meetups-list', templateUrl: './groups-meetups-list.component.html', styleUrls: ['./groups-meetups-list.component.scss'], }) export class GroupsMeetupsListComponent implements OnInit { items$: Observable<Meetup[]>; loading$: Observable<boolean>; constructor(private store: Store) {} ngOnInit() { this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 items$: Observable<Meetup[]>; loading$: Observable<boolean>; import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 9 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 constructor(private store: Store) {} import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 15 16 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 19 } 20 } 21
  58. Stateful import { /* ... */ } from '...'; @Component({

    selector: 'workspace-groups-meetups-list', templateUrl: './groups-meetups-list.component.html', styleUrls: ['./groups-meetups-list.component.scss'], }) export class GroupsMeetupsListComponent implements OnInit { items$: Observable<Meetup[]>; loading$: Observable<boolean>; constructor(private store: Store) {} ngOnInit() { this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 items$: Observable<Meetup[]>; loading$: Observable<boolean>; import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 9 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 constructor(private store: Store) {} import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 15 16 17 18 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); 19 } 20 } 21 this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); import { /* ... */ } from '...'; 1 2 @Component({ 3 selector: 'workspace-groups-meetups-list', 4 templateUrl: './groups-meetups-list.component.html', 5 styleUrls: ['./groups-meetups-list.component.scss'], 6 }) 7 export class GroupsMeetupsListComponent implements OnInit { 8 items$: Observable<Meetup[]>; 9 loading$: Observable<boolean>; 10 11 constructor(private store: Store) {} 12 13 ngOnInit() { 14 this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); 15 16 this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); 17 18 19 } 20 } 21 import { /* ... */ } from '...'; @Component({ selector: 'workspace-groups-meetups-list', templateUrl: './groups-meetups-list.component.html', styleUrls: ['./groups-meetups-list.component.scss'], }) export class GroupsMeetupsListComponent implements OnInit { items$: Observable<Meetup[]>; loading$: Observable<boolean>; constructor(private store: Store) {} ngOnInit() { this.loading$ = this.store.pipe(select(fromGroupStore.selectIsLoading)); this.items$ = this.store.pipe(select(fromGroupStore.selectAllMeetups)); this.store.dispatch(fromGroupStore.getAllMeetupsFromGroup()); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  59. Stateful <div class="container"> <h1 class="mt-3">All Gatherings of the Group</h1> <mat-progress-bar

    *ngIf="loading$ | async; else list" mode="indeterminate" ></mat-progress-bar> <ng-template #list> <workspace-meetup-list [items]="items$ | async"> </workspace-meetup-list> </ng-template> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13
  60. Stateful <div class="container"> <h1 class="mt-3">All Gatherings of the Group</h1> <mat-progress-bar

    *ngIf="loading$ | async; else list" mode="indeterminate" ></mat-progress-bar> <ng-template #list> <workspace-meetup-list [items]="items$ | async"> </workspace-meetup-list> </ng-template> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 <workspace-meetup-list [items]="items$ | async"> </workspace-meetup-list> <div class="container"> 1 <h1 class="mt-3">All Gatherings of the Group</h1> 2 3 <mat-progress-bar 4 *ngIf="loading$ | async; else list" 5 mode="indeterminate" 6 ></mat-progress-bar> 7 8 <ng-template #list> 9 10 11 </ng-template> 12 </div> 13
  61. Stateless import { /* ... */ } from '...'; @Component({

    selector: 'workspace-meetup-list', templateUrl: './meetup-list.component.html', styleUrls: ['./meetup-list.component.scss'], }) export class MeetupListComponent { @Input() items: Meetup[] = []; } 1 2 3 4 5 6 7 8 9 10 11 12
  62. Stateless <div class="mb-20"> <ng-template #noItems> No items found... </ng-template> <div

    *ngIf="items?.length > 0; else noItems"> <ul> <li *ngFor="let item of items"> {{ item.name }} </li> </ul> </div> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13
  63. Stateless <workspace-meetup-item *ngFor="let item of items" [item]="item" > </workspace-meetup-item> <div

    class="mb-20"> 1 <ng-template #noItems> 2 No items found... 3 </ng-template> 4 5 <div *ngIf="items?.length > 0; else noItems"> 6 7 8 9 10 11 </div> 12 </div> 13
  64. Stateless <workspace-meetup-item *ngFor="let item of items" [item]="item" > </workspace-meetup-item> <div

    class="mb-20"> 1 <ng-template #noItems> 2 No items found... 3 </ng-template> 4 5 <div *ngIf="items?.length > 0; else noItems"> 6 7 8 9 10 11 </div> 12 </div> 13 <div class="mb-20"> <ng-template #noItems> No items found... </ng-template> <div *ngIf="items?.length > 0; else noItems"> <workspace-meetup-item *ngFor="let item of items" [item]="item" > </workspace-meetup-item> </div> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13
  65. Stateless <workspace-meetup-item *ngFor="let item of items" [item]="item" (itemClicked)="bubbleNavigateEvent($event)" > <div

    class="mb-20"> 1 <ng-template #noItems> 2 No items found... 3 </ng-template> 4 5 <div *ngIf="items?.length > 0; else noItems"> 6 7 8 9 10 11 </workspace-meetup-item> 12 </div> 13 </div> 14
  66. Stateless <div class="mb-20"> <ng-template #noItems> No items found... </ng-template> <div

    *ngIf="items?.length > 0; else noItems"> <workspace-meetup-item *ngFor="let item of items" [item]="item" (itemClicked)="bubbleNavigateEvent($event)" > </workspace-meetup-item> </div> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  67. Stateless import { /* ... */ } from '...'; @Component({

    selector: 'workspace-meetup-list', templateUrl: './meetup-list.component.html', styleUrls: ['./meetup-list.component.scss'], }) export class MeetupListComponent { @Input() items: Meetup[] = []; @Output() itemClicked = new EventEmitter(); bubbleNavigationEvent(meetup: Meetup) { this.itemClicked.emit(meetup); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  68. Stateful Stateless [ ... ] ( ... )

  69. Stateful Stateless [ ... ] ( ... ) Stateless [

    ... ] ( ... )
  70. Stateful Stateless [ ... ] ( ... ) Stateless [

    ... ] ( ... )
  71. Overlook The dependency graph

  72. “ Nx is built on a technology-agnostic core that maintains

    modular units of code and understands the dependency graph between them. nx.dev
  73. Workspace

  74. What is it? The dependency graph helps you overlook all

    your applications and libraries. You learn about relations and dependencies of each module in your workspace.
  75. Why is it helpful?

  76. Graph

  77. Graph

  78. Graph

  79. Affected Dependency Graph

  80. Why is it helpful?

  81. Restrict Module dependencies

  82. Why is it helpful? Control encapsulation of workspace modules. Prevent

    accidental module pollution.
  83. nx.json eslintrc.json or tslint.json Tag projects nx-enforce-module- boundaries Declare dependencies

  84. nx.json "projects": { { 1 2 "gatherr": { 3 "tags":

    [] 4 }, 5 "gatherr-e2e": { 6 "tags": [], 7 "implicitDependencies": ["gatherr"] 8 }, 9 "auth-feature": { 10 "tags": [] 11 }, 12 "groups-feature": { 13 "tags": [] 14 }, 15 "home-feature": { 16 "tags": [] 17 }, 18 "personal-feature": { 19 "tags": [] 20 }, 21 "profile-feature": { 22 "tags": [] 23 } 24 } 25 } 26
  85. nx.json "projects": { { 1 2 "gatherr": { 3 "tags":

    [] 4 }, 5 "gatherr-e2e": { 6 "tags": [], 7 "implicitDependencies": ["gatherr"] 8 }, 9 "auth-feature": { 10 "tags": [] 11 }, 12 "groups-feature": { 13 "tags": [] 14 }, 15 "home-feature": { 16 "tags": [] 17 }, 18 "personal-feature": { 19 "tags": [] 20 }, 21 "profile-feature": { 22 "tags": [] 23 } 24 } 25 } 26 "gatherr": { "gatherr-e2e": { "auth-feature": { "groups-feature": { "home-feature": { "personal-feature": { "profile-feature": { { 1 "projects": { 2 3 "tags": [] 4 }, 5 6 "tags": [], 7 "implicitDependencies": ["gatherr"] 8 }, 9 10 "tags": [] 11 }, 12 13 "tags": [] 14 }, 15 16 "tags": [] 17 }, 18 19 "tags": [] 20 }, 21 22 "tags": [] 23 } 24 } 25 } 26
  86. nx.json "projects": { { 1 2 "gatherr": { 3 "tags":

    [] 4 }, 5 "gatherr-e2e": { 6 "tags": [], 7 "implicitDependencies": ["gatherr"] 8 }, 9 "auth-feature": { 10 "tags": [] 11 }, 12 "groups-feature": { 13 "tags": [] 14 }, 15 "home-feature": { 16 "tags": [] 17 }, 18 "personal-feature": { 19 "tags": [] 20 }, 21 "profile-feature": { 22 "tags": [] 23 } 24 } 25 } 26 "gatherr": { "gatherr-e2e": { "auth-feature": { "groups-feature": { "home-feature": { "personal-feature": { "profile-feature": { { 1 "projects": { 2 3 "tags": [] 4 }, 5 6 "tags": [], 7 "implicitDependencies": ["gatherr"] 8 }, 9 10 "tags": [] 11 }, 12 13 "tags": [] 14 }, 15 16 "tags": [] 17 }, 18 19 "tags": [] 20 }, 21 22 "tags": [] 23 } 24 } 25 } 26 "tags": [] "tags": [], "tags": [] "tags": [] "tags": [] "tags": [] "tags": [] { 1 "projects": { 2 "gatherr": { 3 4 }, 5 "gatherr-e2e": { 6 7 "implicitDependencies": ["gatherr"] 8 }, 9 "auth-feature": { 10 11 }, 12 "groups-feature": { 13 14 }, 15 "home-feature": { 16 17 }, 18 "personal-feature": { 19 20 }, 21 "profile-feature": { 22 23 } 24 } 25 } 26
  87. nx.json "tags": ["app"] "tags": ["app-feature"] "tags": ["app-feature"] "tags": ["app-feature"] "tags":

    ["app-feature"] "tags": ["app-feature"] { 1 "projects": { 2 "gatherr": { 3 4 }, 5 "gatherr-e2e": { 6 "tags": [], 7 "implicitDependencies": ["gatherr"] 8 }, 9 "auth-feature": { 10 11 }, 12 "groups-feature": { 13 14 }, 15 "home-feature": { 16 17 }, 18 "personal-feature": { 19 20 }, 21 "profile-feature": { 22 23 } 24 } 25 } 26
  88. linter "@nrwl/nx/enforce-module-boundaries": [ { 1 "rules": { 2 3 "error",

    4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15
  89. linter "@nrwl/nx/enforce-module-boundaries": [ { 1 "rules": { 2 3 "error",

    4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "enforceBuildableLibDependency": true, { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15
  90. linter "@nrwl/nx/enforce-module-boundaries": [ { 1 "rules": { 2 3 "error",

    4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "enforceBuildableLibDependency": true, { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "allow": [], { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 "enforceBuildableLibDependency": true, 6 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15
  91. linter "@nrwl/nx/enforce-module-boundaries": [ { 1 "rules": { 2 3 "error",

    4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "enforceBuildableLibDependency": true, { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "allow": [], { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 "enforceBuildableLibDependency": true, 6 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "depConstraints": [ { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15
  92. linter "@nrwl/nx/enforce-module-boundaries": [ { 1 "rules": { 2 3 "error",

    4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "enforceBuildableLibDependency": true, { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "allow": [], { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 "enforceBuildableLibDependency": true, 6 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 "depConstraints": [ { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 } 12 }] 13 } 14 } 15 { "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] } { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 9 10 11 12 }] 13 } 14 } 15
  93. linter { "sourceTag": "app", "onlyDependOnLibsWithTags": ["app-feature"] }] { 1 "rules":

    { 2 "@nrwl/nx/enforce-module-boundaries": [ 3 "error", 4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 }, 12 13 14 15 16 }] 17 } 18 } 19
  94. linter "error", { 1 "rules": { 2 "@nrwl/nx/enforce-module-boundaries": [ 3

    4 { 5 "enforceBuildableLibDependency": true, 6 "allow": [], 7 "depConstraints": [ 8 { 9 "sourceTag": "*", 10 "onlyDependOnLibsWithTags": ["*"] 11 }, 12 { 13 "sourceTag": "app", 14 "onlyDependOnLibsWithTags": ["app-feature"] 15 }] 16 }] 17 } 18 } 19
  95. Wildcards https://nx.dev/latest/angular/structure/monorepo-tags#exceptions

  96. None
  97. Speed Up Optimize Build- & Test- Runs

  98. None
  99. None
  100. None
  101. None
  102. None
  103. None
  104. None
  105. "paths": { "@workspace/shared/ui-common": ["libs/shared/ui-common/src/index.ts"], "@workspace/shared/ui-layout": ["libs/shared/ui-layout/src/index.ts"], "@workspace/shared/data": ["libs/shared/data/src/index.ts"], "@workspace/shared/utils": ["libs/shared/utils/src/index.ts"],

    "@workspace/category/data": ["libs/category/data/src/index.ts"], "@workspace/category/api": ["libs/category/api/src/index.ts"], "@workspace/auth/api": ["libs/auth/api/src/index.ts"], "@workspace/auth/feature": ["libs/auth/feature/src/index.ts"], "@workspace/auth/util": ["libs/auth/util/src/index.ts"], "@workspace/auth/data": ["libs/auth/data/src/index.ts"], "@workspace/home/feature": ["libs/home/feature/src/index.ts"], "@workspace/home/ui": ["libs/home/ui/src/index.ts"], "@workspace/maps/util": ["libs/maps/util/src/index.ts"], "@workspace/groups/api": ["libs/groups/api/src/index.ts"], "@workspace/groups/feature": ["libs/groups/feature/src/index.ts"], "@workspace/groups/ui": ["libs/groups/ui/src/index.ts"], "@workspace/groups/data": ["libs/groups/data/src/index.ts"], "@workspace/personal/feature": ["libs/personal/feature/src/index.ts"], { 1 "compileOnSave": false, 2 "compilerOptions": { 3 "rootDir": ".", 4 "sourceMap": true, 5 "declaration": false, 6 "moduleResolution": "node", 7 "emitDecoratorMetadata": true, 8 "experimentalDecorators": true, 9 "importHelpers": true, 10 "target": "es5", 11 "module": "esnext", 12 "typeRoots": ["node_modules/@types"], 13 "lib": ["es2017", "dom"], 14 "skipLibCheck": true, 15 "skipDefaultLibCheck": true, 16 "baseUrl": ".", 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
  106. libs/shared-utils

  107. libs/shared-utils

  108. libs/shared-utils /ServiceA libs/shared-utils

  109. libs/shared-utils /ServiceA libs/shared-utils /ServiceB libs/shared-utils

  110. Changes on Service A or B builds both consumers!

  111. Move Command nx g @nrwl/angular:move --project my-feature-lib shared/my-feature-lib 1

  112. NX Plugins Cypress, Storybook, ...

  113. Wrap up

  114. Wrap up Workspace Enterprise Applications

  115. Wrap up Workspace Enterprise Applications Latest Toolset

  116. Wrap up Workspace Enterprise Applications Latest Toolset Affected Build &

    Cache
  117. Wrap up Workspace Enterprise Applications Latest Toolset Affected Build &

    Cache SoC through Libs
  118. Wrap up Workspace Enterprise Applications Latest Toolset Affected Build &

    Cache SoC through Libs Dep Graph
  119. Wrap up Workspace Enterprise Applications Latest Toolset Affected Build &

    Cache SoC through Libs Dep Graph Plugin Architecture
  120. Fabian Gosebrink @FabianGosebrink

  121. Thank you!