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.

Fabian Gosebrink

April 26, 2022
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. @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
  11. @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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. “ Nx is built on a technology-agnostic core that maintains

    modular units of code and understands the dependency graph between them. nx.dev
  28. 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.
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. "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
  41. Wrap up Workspace Enterprise Applications Latest Toolset Affected Build &

    Cache SoC through Libs Dep Graph Plugin Architecture