Save 37% off PRO during our Black Friday Sale! »

NX-Workspace: Simplified scaling for large Angular Projects

NX-Workspace: Simplified scaling for large Angular Projects

Production build and testing takes longer and longer to execute. It takes more and more time to find the right component or the desired service, the effort for on-boarding new colleagues grows. What does this tell you? That's right, you're working on a big Angular project - no more, no less. When the complexity of a project grows rapidly, one thing is needed: focus on the essentials without repetitive, manual tuning of the CI/CD pipeline. NX provides solutions for extending, testing and publishing your Angular project. In this talk, you'll learn how to organize multiple builds and unit & UI testing with NX. You'll see NX's extensions for central state management in action. You'll also see StoryBook's integration with Nx for living and up-to-date documentation of your component library. Another tool you'll learn about is Workspace Schematics, which allows you to further automate the development process.

Fb89953d3a1b1fcb862a186585c37c25?s=128

Fabian Gosebrink

April 27, 2021
Tweet

Transcript

  1. NX WORKSPACES FOR ANGULAR

  2. What is NX?

  3. What is NX? Overview

  4. What is NX? Overview Encapsulation

  5. What is NX? Overview Encapsulation Speed

  6. What is NX? Overview Encapsulation Speed Migration

  7. What is NX? Overview Encapsulation Speed Migration Plugins

  8. Why?

  9. App

  10. App Funktion Pipe Service Component / Directive Module

  11. App Funktion Pipe Service Component / Directive Module

  12. App Funktion Pipe Service Component / Directive Module

  13. App Funktion Pipe Service Component / Directive Module

  14. App Funktion Pipe Service Component / Directive Module

  15. App Funktion Pipe Service Component / Directive Module

  16. App Funktion Pipe Service Component / Directive Module

  17. App Funktion Pipe Service Component / Directive Module

  18. App Funktion Pipe Service Component / Directive Module

  19. App Funktion Pipe Service Component / Directive Module

  20. App

  21. App

  22. Bring Order

  23. Have Flexibility

  24. Gregor Woiwode

  25. Fabian Gosebrink

  26. None
  27. Fabian Gosebrink

  28. Fabian Gosenbrink

  29. None
  30. What is

  31. Tooling

  32. Tooling

  33. Tooling

  34. Tooling

  35. NG Workspace

  36. NG Workspace

  37. NG Workspace

  38. NX Workspace

  39. Workspace

  40. Intelligent Build

  41. Intelligent Build Cache

  42. Encapsulation

  43. Angular Modules

  44. Feature Modules

  45. 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
  46. 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
  47. import { NgModule } from '@angular/core'; import { CommonModule }

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

    from '@angular/common'; @NgModule({ imports: [ CommonModule ], declarations: [ Feature2Component1, Feature2Component2, Feature2Component3, Feature2Component4, Feature2Component5, Feature2Component6, ] }) export class Feature2Module { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  49. 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
  50. 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
  51. 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
  52. Libraries

  53. Libraries Share code between apps

  54. None
  55. None
  56. None
  57. None
  58. 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
  59. 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
  60. @NgModule({ declarations: [LibToConfigureComponent], imports: [CommonModule], exports: [LibToConfigureComponent], }) export class

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

    LibToConfigureModule {} 1 2 3 4 5 6 @NgModule({ declarations: [AppComponent], imports: [BrowserModule, LibToConfigureModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {} 1 2 3 4 5 6 7
  62. None
  63. None
  64. None
  65. None
  66. None
  67. None
  68. Rethinking Libraries

  69. Libraries Share code between apps

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

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

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

  73. None
  74. None
  75. Folders

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

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

    Feature
  78. Feature Ui vs.

  79. Container Presentational vs.

  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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
  88. 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
  89. 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 14
  90. Stateless <workspace-meetup-item *ngFor="let item of items" [item]="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 </workspace-meetup-item> 11 </div> 12 </div> 13
  91. Stateless <workspace-meetup-item *ngFor="let item of items" [item]="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 </workspace-meetup-item> 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
  92. Stateless <workspace-meetup-item *ngFor="let item of items" [item]="item" (itemClicked)="navigateToItem($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
  93. 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
  94. 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
  95. Stateful Stateless [ ... ] ( ... )

  96. Stateful Stateless [ ... ] ( ... ) Stateless [

    ... ] ( ... )
  97. Stateful Stateless [ ... ] ( ... ) Stateless [

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

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

    modular units of code and understands the dependency graph between them. nx.dev
  100. Dependency Graph

  101. 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.
  102. Why is it helpful? On-Boarding new colleagues Cyclic library dependency

  103. Graph

  104. Graph

  105. Graph

  106. Command nx dep-graph 1 2 > NX NOTE Dep graph

    started at http://127.0.0.1:4211 3
  107. Command nx dep-graph 1 2 > NX NOTE Dep graph

    started at http://127.0.0.1:4211 3 > NX NOTE Dep graph started at http://127.0.0.1:4211 nx dep-graph 1 2 3
  108. Affected Dependency Graph

  109. Why is it helpful? See impact a library has on

    the workspace. Optimize build, lint, serve, test commands.
  110. main feature HEAD nx affected:dep-graph \ --base main How it

    works Analyse differences between git branches
  111. Command nx affected:dep-graph 1 \ --base <branch-name> # default: main

    2 \ --head <branch-comparing> # default: HEAD 3 4 > NX NOTE Dep graph started at http://127.0.0.1:4211 5
  112. Command nx affected:dep-graph 1 \ --base <branch-name> # default: main

    2 \ --head <branch-comparing> # default: HEAD 3 4 > NX NOTE Dep graph started at http://127.0.0.1:4211 5 \ --head <branch-comparing> # default: HEAD nx affected:dep-graph 1 \ --base <branch-name> # default: main 2 3 4 > NX NOTE Dep graph started at http://127.0.0.1:4211 5
  113. DEMO

  114. Restrict Module dependencies

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

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

  117. 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
  118. 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
  119. 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
  120. 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
  121. 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
  122. 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
  123. 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
  124. 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
  125. 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
  126. 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
  127. 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
  128. Wildcards https://nx.dev/latest/angular/structure/monorepo-tags#exceptions

  129. None
  130. linter "warn", { 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
  131. None
  132. DEMO

  133. Speed Up Optimize Build- & Test- Runs

  134. None
  135. None
  136. None
  137. None
  138. None
  139. None
  140. None
  141. "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
  142. 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
  143. libs/shared-utils

  144. libs/shared-utils

  145. libs/shared-utils /ServiceA libs/shared-utils

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

  147. Lib1 Lib2

  148. Changes on Service A or B builds both libs!

  149. libs/shared-utils

  150. libs/shared-utils Use with caution!

  151. libs/<appName>/<libname> App Specific Library libs/<featureName>/<libname>

  152. libs/shared/<appName>/<libname> Generic for one App libs/shared/<libname>

  153. libs/shared/feature-logging libs/shared/feature-notification libs/shared/feature-... Generic for x Apps

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

  155. Migration Update your Workspace

  156. Why is it helpful? Benefit from automatic migration scripts. Get

    new features enabling your team building more efficient solutions.
  157. Prepare Migration nx migrate Update package.json Identify available migrations Write

    migrations.json Preparation complete
  158. Execute Migration Install packages (npm, ...) nx migrate --run-migrations=migrations.json nx

    migrate latest
  159. nx migrate latest 1 2 > NX NOTE Dep graph

    started at http://127.0.0.1:4211 3 Command
  160. nx migrate latest 1 2 > NX NOTE Dep graph

    started at http://127.0.0.1:4211 3 > NX NOTE Dep graph started at http://127.0.0.1:4211 nx migrate latest 1 2 3 Command
  161. ↑ nx migrate latest Fetching meta data about packages. It

    may take a few minutes. Fetching @nrwl/workspace@latest Fetching @nrwl/tao@12.0.8 ... Fetching typescript@4.1.4 Fetching @types/node@14.14.33 Fetching prettier@2.2.1 Fetching dotenv@8.2.0 .... Fetching @angular/cli@^11.2.0 ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 > NX The migrate command has run successfully. 18 19 - package.json has been updated 20 - migrations.json has been generated 21 22 > NX Next steps: 23 24 - Make sure package.json changes make sense and then run 25 - Run 'nx migrate --run-migrations=migrations.json' 26 Prepare Migration
  162. ↑ nx migrate latest Fetching meta data about packages. It

    may take a few minutes. Fetching @nrwl/workspace@latest Fetching @nrwl/tao@12.0.8 ... Fetching typescript@4.1.4 Fetching @types/node@14.14.33 Fetching prettier@2.2.1 Fetching dotenv@8.2.0 .... Fetching @angular/cli@^11.2.0 ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 > NX The migrate command has run successfully. 18 19 - package.json has been updated 20 - migrations.json has been generated 21 22 > NX Next steps: 23 24 - Make sure package.json changes make sense and then run 25 - Run 'nx migrate --run-migrations=migrations.json' 26 - migrations.json has been generated ↑ nx migrate latest 1 2 Fetching meta data about packages. 3 It may take a few minutes. 4 Fetching @nrwl/workspace@latest 5 Fetching @nrwl/tao@12.0.8 6 ... 7 8 Fetching typescript@4.1.4 9 Fetching @types/node@14.14.33 10 Fetching prettier@2.2.1 11 Fetching dotenv@8.2.0 12 .... 13 14 Fetching @angular/cli@^11.2.0 15 ... 16 17 > NX The migrate command has run successfully. 18 19 - package.json has been updated 20 21 22 > NX Next steps: 23 24 - Make sure package.json changes make sense and then run 25 - Run 'nx migrate --run-migrations=migrations.json' 26 Prepare Migration
  163. ↑ nx migrate latest Fetching meta data about packages. It

    may take a few minutes. Fetching @nrwl/workspace@latest Fetching @nrwl/tao@12.0.8 ... Fetching typescript@4.1.4 Fetching @types/node@14.14.33 Fetching prettier@2.2.1 Fetching dotenv@8.2.0 .... Fetching @angular/cli@^11.2.0 ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 > NX The migrate command has run successfully. 18 19 - package.json has been updated 20 - migrations.json has been generated 21 22 > NX Next steps: 23 24 - Make sure package.json changes make sense and then run 25 - Run 'nx migrate --run-migrations=migrations.json' 26 - migrations.json has been generated ↑ nx migrate latest 1 2 Fetching meta data about packages. 3 It may take a few minutes. 4 Fetching @nrwl/workspace@latest 5 Fetching @nrwl/tao@12.0.8 6 ... 7 8 Fetching typescript@4.1.4 9 Fetching @types/node@14.14.33 10 Fetching prettier@2.2.1 11 Fetching dotenv@8.2.0 12 .... 13 14 Fetching @angular/cli@^11.2.0 15 ... 16 17 > NX The migrate command has run successfully. 18 19 - package.json has been updated 20 21 22 > NX Next steps: 23 24 - Make sure package.json changes make sense and then run 25 - Run 'nx migrate --run-migrations=migrations.json' 26 - Run 'nx migrate --run-migrations=migrations.json' ↑ nx migrate latest 1 2 Fetching meta data about packages. 3 It may take a few minutes. 4 Fetching @nrwl/workspace@latest 5 Fetching @nrwl/tao@12.0.8 6 ... 7 8 Fetching typescript@4.1.4 9 Fetching @types/node@14.14.33 10 Fetching prettier@2.2.1 11 Fetching dotenv@8.2.0 12 .... 13 14 Fetching @angular/cli@^11.2.0 15 ... 16 17 > NX The migrate command has run successfully. 18 19 - package.json has been updated 20 - migrations.json has been generated 21 22 > NX Next steps: 23 24 - Make sure package.json changes make sense and then run 25 26 Prepare Migration
  164. "migrations": [ ] { 1 2 { 3 "version": "12.x.x",

    4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 11 } 12 migrations.json
  165. "migrations": [ ] { 1 2 { 3 "version": "12.x.x",

    4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 11 } 12 "version": "12.x.x", { 1 "migrations": [ 2 { 3 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 migrations.json
  166. "migrations": [ ] { 1 2 { 3 "version": "12.x.x",

    4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 11 } 12 "version": "12.x.x", { 1 "migrations": [ 2 { 3 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "description": "Update the decoration script when using Angular CLI", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 migrations.json
  167. "migrations": [ ] { 1 2 { 3 "version": "12.x.x",

    4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 11 } 12 "version": "12.x.x", { 1 "migrations": [ 2 { 3 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "description": "Update the decoration script when using Angular CLI", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 "description": "Update the decoration script when using Angular CLI", 5 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 migrations.json
  168. "migrations": [ ] { 1 2 { 3 "version": "12.x.x",

    4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 11 } 12 "version": "12.x.x", { 1 "migrations": [ 2 { 3 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "description": "Update the decoration script when using Angular CLI", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 "description": "Update the decoration script when using Angular CLI", 5 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "package": "@nrwl/workspace", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 migrations.json
  169. "migrations": [ ] { 1 2 { 3 "version": "12.x.x",

    4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 11 } 12 "version": "12.x.x", { 1 "migrations": [ 2 { 3 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "description": "Update the decoration script when using Angular CLI", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 "description": "Update the decoration script when using Angular CLI", 5 6 "package": "@nrwl/workspace", 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "package": "@nrwl/workspace", { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 7 "name": "update-decorate-angular-cli" 8 }, 9 ... 10 ] 11 } 12 "name": "update-decorate-angular-cli" { 1 "migrations": [ 2 { 3 "version": "12.x.x", 4 "description": "Update the decoration script when using Angular CLI", 5 "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 6 "package": "@nrwl/workspace", 7 8 }, 9 ... 10 ] 11 } 12 migrations.json
  170. > NX Running migrations from 'migrations.json' 1 2 Running migration

    rename-workspace-schematic-script 3 Successfully finished rename-workspace-schematic-script 4 --------------------------------------------------------- 5 Running migration update-ngcc-postinstall 6 Successfully finished update-ngcc-postinstall 7 --------------------------------------------------------- 8 Running migration migration-v11.1-can-activate-with-redirect-to 9 Successfully finished migration-v11.1-can-activate-with-redirect-to 10 --------------------------------------------------------- 11 Running migration always-use-project-level-tsconfigs-with-eslint 12 Successfully finished always-use-project-level-tsconfigs-with-eslint 13 --------------------------------------------------------- 14 15 > NX Successfully finished running migrations from 'migrations.json' 16 Execute Migration
  171. > NX Running migrations from 'migrations.json' 1 2 Running migration

    rename-workspace-schematic-script 3 Successfully finished rename-workspace-schematic-script 4 --------------------------------------------------------- 5 Running migration update-ngcc-postinstall 6 Successfully finished update-ngcc-postinstall 7 --------------------------------------------------------- 8 Running migration migration-v11.1-can-activate-with-redirect-to 9 Successfully finished migration-v11.1-can-activate-with-redirect-to 10 --------------------------------------------------------- 11 Running migration always-use-project-level-tsconfigs-with-eslint 12 Successfully finished always-use-project-level-tsconfigs-with-eslint 13 --------------------------------------------------------- 14 15 > NX Successfully finished running migrations from 'migrations.json' 16 Running migration rename-workspace-schematic-script Running migration update-ngcc-postinstall Running migration migration-v11.1-can-activate-with-redirect-to Running migration always-use-project-level-tsconfigs-with-eslint > NX Running migrations from 'migrations.json' 1 2 3 Successfully finished rename-workspace-schematic-script 4 --------------------------------------------------------- 5 6 Successfully finished update-ngcc-postinstall 7 --------------------------------------------------------- 8 9 Successfully finished migration-v11.1-can-activate-with-redirect-to 10 --------------------------------------------------------- 11 12 Successfully finished always-use-project-level-tsconfigs-with-eslint 13 --------------------------------------------------------- 14 15 > NX Successfully finished running migrations from 'migrations.json' 16 Execute Migration
  172. > NX Running migrations from 'migrations.json' 1 2 Running migration

    rename-workspace-schematic-script 3 Successfully finished rename-workspace-schematic-script 4 --------------------------------------------------------- 5 Running migration update-ngcc-postinstall 6 Successfully finished update-ngcc-postinstall 7 --------------------------------------------------------- 8 Running migration migration-v11.1-can-activate-with-redirect-to 9 Successfully finished migration-v11.1-can-activate-with-redirect-to 10 --------------------------------------------------------- 11 Running migration always-use-project-level-tsconfigs-with-eslint 12 Successfully finished always-use-project-level-tsconfigs-with-eslint 13 --------------------------------------------------------- 14 15 > NX Successfully finished running migrations from 'migrations.json' 16 Running migration rename-workspace-schematic-script Running migration update-ngcc-postinstall Running migration migration-v11.1-can-activate-with-redirect-to Running migration always-use-project-level-tsconfigs-with-eslint > NX Running migrations from 'migrations.json' 1 2 3 Successfully finished rename-workspace-schematic-script 4 --------------------------------------------------------- 5 6 Successfully finished update-ngcc-postinstall 7 --------------------------------------------------------- 8 9 Successfully finished migration-v11.1-can-activate-with-redirect-to 10 --------------------------------------------------------- 11 12 Successfully finished always-use-project-level-tsconfigs-with-eslint 13 --------------------------------------------------------- 14 15 > NX Successfully finished running migrations from 'migrations.json' 16 > NX Successfully finished running migrations from 'migrations.json' > NX Running migrations from 'migrations.json' 1 2 Running migration rename-workspace-schematic-script 3 Successfully finished rename-workspace-schematic-script 4 --------------------------------------------------------- 5 Running migration update-ngcc-postinstall 6 Successfully finished update-ngcc-postinstall 7 --------------------------------------------------------- 8 Running migration migration-v11.1-can-activate-with-redirect-to 9 Successfully finished migration-v11.1-can-activate-with-redirect-to 10 --------------------------------------------------------- 11 Running migration always-use-project-level-tsconfigs-with-eslint 12 Successfully finished always-use-project-level-tsconfigs-with-eslint 13 --------------------------------------------------------- 14 15 16 Execute Migration
  173. Caution Migrations can fail. Be aware of script errors or

    situations NX cannot automatically complete the migration for you.
  174. Missing @nrwl/tao https://github.com/nrwl/nx/issues/2120 A misconfigured dependency prevented the migration from

    running. npm install --save-dev @nrwl/tao 1 FIX
  175. Trouble- shooting - Read errors carefully. - Check GitHub for

    existing issues. - File an issue if you have encountered a new bug.
  176. ESLint Updating to the latest NX workspace allows to migrating

    from TSLint to ESLint automatically. You can migrate one project after another to split the effort.
  177. nx list @nrwl/angular 1 2 > NX Capabilities in @nrwl/angular:

    3 4 GENERATORS 5 6 init : Initialize the @nrwl/angular plugin 7 application : Create an Angular application 8 library : Create an Angular library 9 karma : Add karma configuration to a workspace 10 karma-project : Add karma testing to a project 11 ngrx : Add an ngrx config to a project 12 downgrade-module : Setup Downgrade Module 13 upgrade-module : Add an upgrade module 14 storybook-configuration : Create stories/specs for all components declared in a library 15 storybook-migrate-defaults-5-to-6 : Generate default Storybook configuration files using Story 16 stories : Create stories/specs for all components declared in a library 17 component-cypress-spec : Create a cypress spec for a ui component that has a story 18 component-story : Create a stories.ts file for a component 19 move : Move an Angular application or library to another folder 20 add-linting : Add linting configuration to an Angular project 21 convert-tslint-to-eslint : Convert a project from TSLint to ESLint 22 List nx commands
  178. nx list @nrwl/angular 1 2 > NX Capabilities in @nrwl/angular:

    3 4 GENERATORS 5 6 init : Initialize the @nrwl/angular plugin 7 application : Create an Angular application 8 library : Create an Angular library 9 karma : Add karma configuration to a workspace 10 karma-project : Add karma testing to a project 11 ngrx : Add an ngrx config to a project 12 downgrade-module : Setup Downgrade Module 13 upgrade-module : Add an upgrade module 14 storybook-configuration : Create stories/specs for all components declared in a library 15 storybook-migrate-defaults-5-to-6 : Generate default Storybook configuration files using Story 16 stories : Create stories/specs for all components declared in a library 17 component-cypress-spec : Create a cypress spec for a ui component that has a story 18 component-story : Create a stories.ts file for a component 19 move : Move an Angular application or library to another folder 20 add-linting : Add linting configuration to an Angular project 21 convert-tslint-to-eslint : Convert a project from TSLint to ESLint 22 init : Initialize the @nrwl/angular plugin application : Create an Angular application library : Create an Angular library karma : Add karma configuration to a workspace karma-project : Add karma testing to a project ngrx : Add an ngrx config to a project downgrade-module : Setup Downgrade Module upgrade-module : Add an upgrade module storybook-configuration : Create stories/specs for all components declared in a library storybook-migrate-defaults-5-to-6 : Generate default Storybook configuration files using Story stories : Create stories/specs for all components declared in a library component-cypress-spec : Create a cypress spec for a ui component that has a story component-story : Create a stories.ts file for a component move : Move an Angular application or library to another folder add-linting : Add linting configuration to an Angular project convert-tslint-to-eslint : Convert a project from TSLint to ESLint nx list @nrwl/angular 1 2 > NX Capabilities in @nrwl/angular: 3 4 GENERATORS 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 List nx commands
  179. nx list @nrwl/angular 1 2 > NX Capabilities in @nrwl/angular:

    3 4 GENERATORS 5 6 init : Initialize the @nrwl/angular plugin 7 application : Create an Angular application 8 library : Create an Angular library 9 karma : Add karma configuration to a workspace 10 karma-project : Add karma testing to a project 11 ngrx : Add an ngrx config to a project 12 downgrade-module : Setup Downgrade Module 13 upgrade-module : Add an upgrade module 14 storybook-configuration : Create stories/specs for all components declared in a library 15 storybook-migrate-defaults-5-to-6 : Generate default Storybook configuration files using Story 16 stories : Create stories/specs for all components declared in a library 17 component-cypress-spec : Create a cypress spec for a ui component that has a story 18 component-story : Create a stories.ts file for a component 19 move : Move an Angular application or library to another folder 20 add-linting : Add linting configuration to an Angular project 21 convert-tslint-to-eslint : Convert a project from TSLint to ESLint 22 init : Initialize the @nrwl/angular plugin application : Create an Angular application library : Create an Angular library karma : Add karma configuration to a workspace karma-project : Add karma testing to a project ngrx : Add an ngrx config to a project downgrade-module : Setup Downgrade Module upgrade-module : Add an upgrade module storybook-configuration : Create stories/specs for all components declared in a library storybook-migrate-defaults-5-to-6 : Generate default Storybook configuration files using Story stories : Create stories/specs for all components declared in a library component-cypress-spec : Create a cypress spec for a ui component that has a story component-story : Create a stories.ts file for a component move : Move an Angular application or library to another folder add-linting : Add linting configuration to an Angular project convert-tslint-to-eslint : Convert a project from TSLint to ESLint nx list @nrwl/angular 1 2 > NX Capabilities in @nrwl/angular: 3 4 GENERATORS 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 convert-tslint-to-eslint : Convert a project from TSLint to ESLint nx list @nrwl/angular 1 2 > NX Capabilities in @nrwl/angular: 3 4 GENERATORS 5 6 init : Initialize the @nrwl/angular plugin 7 application : Create an Angular application 8 library : Create an Angular library 9 karma : Add karma configuration to a workspace 10 karma-project : Add karma testing to a project 11 ngrx : Add an ngrx config to a project 12 downgrade-module : Setup Downgrade Module 13 upgrade-module : Add an upgrade module 14 storybook-configuration : Create stories/specs for all components declared in a library 15 storybook-migrate-defaults-5-to-6 : Generate default Storybook configuration files using Story 16 stories : Create stories/specs for all components declared in a library 17 component-cypress-spec : Create a cypress spec for a ui component that has a story 18 component-story : Create a stories.ts file for a component 19 move : Move an Angular application or library to another folder 20 add-linting : Add linting configuration to an Angular project 21 22 List nx commands
  180. nx g convert-tslint-to-eslint --project <project> 1 DELETE <project>/tslint.json CREATE <project>/.eslintrc.json

    UPDATE angular.json UPDATE .eslintrc.json Migrate to ESLint
  181. NX Plugins Cypress, Storybook, Bring your own

  182. Plugins Nx is a plugin system. nx list 1 List

    all available plugins
  183. Protractor Your choice

  184. Protractor Your choice Deprecation Note https://github.com/angular/protractor/issues/5502

  185. Story Book

  186. Why is it helpful? Benefit from automatic migration scripts. Get

    new features enabling your team building more efficient solutions.
  187. None
  188. Install https://nx.dev/latest/angular/storybook/overview

  189. Configure

  190. Configure

  191. Configure

  192. Write

  193. Write

  194. Write

  195. Write

  196. Write

  197. Run

  198. DEMO

  199. Wrap up

  200. Wrap up

  201. Wrap up Workspace Enterprise Applications

  202. Wrap up Workspace Enterprise Applications Latest Toolset

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

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

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

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

    Cache SoC through Libs Dep Graph Two-Step Migration
  207. Wrap up Workspace Enterprise Applications Latest Toolset Affected Build &

    Cache SoC through Libs Dep Graph Two-Step Migration Plugin Architecture
  208. Next Talk...

  209. Thank you!