$30 off During Our Annual Pro Sale. View Details »

Create modern Web Apps with the new Angular and it's ecosystem

Create modern Web Apps with the new Angular and it's ecosystem

Architecting scalable and maintainable front-end solutions for complex web applications is a significant challenge. As applications evolve in size and complexity, effective state management, handling asynchronous operations, and ensuring code maintainability become crucial considerations.

In this talk, Fabian Gosebrink dives into the world of Angular architectures, unveiling a transformative approach to overcome these challenges. By harnessing the power of signals, NgRx, and leveraging cutting-edge features such as standalone components and functional APIs, attendees will gain insights into constructing resilient and high-performing front-end solutions. Through practical examples and real-world experiences, Fabian demonstrates how signals can effectively decouple components and streamline intricate workflows, while NgRx offers a centralized and predictable state management solution. Furthermore, the integration of standalone components and functional APIs enhances code maintainability and fosters reusability. By the end of this session, participants will be able to architect robust and efficient front-end solutions, enabling them to confidently navigate the complexities of building scalable and maintainable applications in the ever-evolving web development landscape.

Fabian Gosebrink

September 05, 2023
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. Modern Apps

    View Slide

  2. View Slide

  3. - Winston Churchill

    View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. Fabian Gosebrink
    https://offering.solutions
    Google Developer
    Expert
    Microsoft MVP Pluralsight Author

    View Slide

  15. View Slide

  16. View Slide

  17. main.ts
    app.module.ts
    app.component.ts

    View Slide

  18. main.ts
    app.module.ts
    app.component.ts

    View Slide

  19. platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch((err) => console.error(err));
    1
    2
    3

    View Slide

  20. import { NgModule } from '@angular/...';
    import { BrowserModule } from '@angular/...';
    import { AppComponent } from './app.component';
    @NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule],
    providers: [],
    bootstrap: [AppComponent],
    })
    export class AppModule {}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    View Slide

  21. import { Component } from '@angular/core';
    @Component({
    selector: 'app-root',
    template: `{{ title }}`,
    styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
    title = 'oldangularapp';
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  22. import { bootstrapApplication } from '@angular/platform-browser';
    import { ImageGridComponent } from'./image-grid';
    @Component({
    standalone: true,
    selector: 'photo-gallery',
    imports: [ImageGridComponent],
    template: `

    `,
    })
    export class PhotoGalleryComponent {
    // component logic
    }
    bootstrapApplication(PhotoGalleryComponent);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    View Slide

  23. import { bootstrapApplication } from '@angular/platform-browser';
    import { ImageGridComponent } from'./image-grid';
    @Component({
    standalone: true,
    selector: 'photo-gallery',
    imports: [ImageGridComponent],
    template: `

    `,
    })
    export class PhotoGalleryComponent {
    // component logic
    }
    bootstrapApplication(PhotoGalleryComponent);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    standalone: true,
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    5
    selector: 'photo-gallery',
    6
    imports: [ImageGridComponent],
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16

    View Slide

  24. import { bootstrapApplication } from '@angular/platform-browser';
    import { ImageGridComponent } from'./image-grid';
    @Component({
    standalone: true,
    selector: 'photo-gallery',
    imports: [ImageGridComponent],
    template: `

    `,
    })
    export class PhotoGalleryComponent {
    // component logic
    }
    bootstrapApplication(PhotoGalleryComponent);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    standalone: true,
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    5
    selector: 'photo-gallery',
    6
    imports: [ImageGridComponent],
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16
    imports: [ImageGridComponent],
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    standalone: true,
    5
    selector: 'photo-gallery',
    6
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16

    View Slide

  25. import { bootstrapApplication } from '@angular/platform-browser';
    import { ImageGridComponent } from'./image-grid';
    @Component({
    standalone: true,
    selector: 'photo-gallery',
    imports: [ImageGridComponent],
    template: `

    `,
    })
    export class PhotoGalleryComponent {
    // component logic
    }
    bootstrapApplication(PhotoGalleryComponent);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    standalone: true,
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    5
    selector: 'photo-gallery',
    6
    imports: [ImageGridComponent],
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16
    imports: [ImageGridComponent],
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    standalone: true,
    5
    selector: 'photo-gallery',
    6
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16
    export class PhotoGalleryComponent {
    // component logic
    }
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    standalone: true,
    5
    selector: 'photo-gallery',
    6
    imports: [ImageGridComponent],
    7
    template: `
    8

    9
    `,
    10
    })
    11
    12
    13
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16

    View Slide

  26. import { bootstrapApplication } from '@angular/platform-browser';
    import { ImageGridComponent } from'./image-grid';
    @Component({
    standalone: true,
    selector: 'photo-gallery',
    imports: [ImageGridComponent],
    template: `

    `,
    })
    export class PhotoGalleryComponent {
    // component logic
    }
    bootstrapApplication(PhotoGalleryComponent);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    standalone: true,
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    5
    selector: 'photo-gallery',
    6
    imports: [ImageGridComponent],
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16
    imports: [ImageGridComponent],
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    standalone: true,
    5
    selector: 'photo-gallery',
    6
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16
    export class PhotoGalleryComponent {
    // component logic
    }
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    standalone: true,
    5
    selector: 'photo-gallery',
    6
    imports: [ImageGridComponent],
    7
    template: `
    8

    9
    `,
    10
    })
    11
    12
    13
    14
    15
    bootstrapApplication(PhotoGalleryComponent);
    16 bootstrapApplication(PhotoGalleryComponent);
    import { bootstrapApplication } from '@angular/platform-browser';
    1
    import { ImageGridComponent } from'./image-grid';
    2
    3
    @Component({
    4
    standalone: true,
    5
    selector: 'photo-gallery',
    6
    imports: [ImageGridComponent],
    7
    template: `
    8

    9
    `,
    10
    })
    11
    export class PhotoGalleryComponent {
    12
    // component logic
    13
    }
    14
    15
    16

    View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. ng generate @angular/core:standalone

    View Slide

  36. Convert all components, directives, and
    pipes to standalone
    Remove unnecessary NgModule classes
    Bootstrap the application using
    standalone APIs

    View Slide

  37. View Slide

  38. export const appConfig: ApplicationConfig = {
    providers: [
    // ...
    importProvidersFrom(
    ToastrModule.forRoot({ /* ... */ }),
    BrowserAnimationsModule
    ),
    ],
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  39. export const appConfig: ApplicationConfig = {
    providers: [
    // ...
    importProvidersFrom(
    ToastrModule.forRoot({ /* ... */ }),
    BrowserAnimationsModule
    ),
    ],
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    importProvidersFrom(
    ToastrModule.forRoot({ /* ... */ }),
    BrowserAnimationsModule
    ),
    export const appConfig: ApplicationConfig = {
    1
    providers: [
    2
    // ...
    3
    4
    5
    6
    7
    ],
    8
    };
    9

    View Slide

  40. export const appConfig: ApplicationConfig = {
    providers: [
    // ...
    provideRouter(APP_ROUTES),
    provideHttpClient(
    withInterceptors([...]),
    ),
    importProvidersFrom(
    ToastrModule.forRoot({ /* ... */ }),
    BrowserAnimationsModule
    ),
    ],
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    View Slide

  41. export const appConfig: ApplicationConfig = {
    providers: [
    // ...
    provideRouter(APP_ROUTES),
    provideHttpClient(
    withInterceptors([...]),
    ),
    importProvidersFrom(
    ToastrModule.forRoot({ /* ... */ }),
    BrowserAnimationsModule
    ),
    ],
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    provideRouter(APP_ROUTES),
    provideHttpClient(
    withInterceptors([...]),
    ),
    export const appConfig: ApplicationConfig = {
    1
    providers: [
    2
    // ...
    3
    4
    5
    6
    7
    8
    importProvidersFrom(
    9
    ToastrModule.forRoot({ /* ... */ }),
    10
    BrowserAnimationsModule
    11
    ),
    12
    ],
    13
    };
    14

    View Slide

  42. provideRouter(APP_ROUTES),
    provideHttpClient(
    withInterceptors([...]),
    withFetch()
    ),
    export const appConfig: ApplicationConfig = {
    1
    providers: [
    2
    // ...
    3
    4
    5
    6
    7
    8
    importProvidersFrom(
    9
    ToastrModule.forRoot({ /* ... */ }),
    10
    BrowserAnimationsModule
    11
    ),
    12
    ],
    13
    };
    14

    View Slide

  43. provideAuth({ /* ... */ }),
    export const appConfig: ApplicationConfig = {
    1
    providers: [
    2
    // ...
    3
    provideRouter(APP_ROUTES),
    4
    provideHttpClient(
    5
    withInterceptors([...]),
    6
    withFetch()
    7
    ),
    8
    9
    importProvidersFrom(
    10
    ToastrModule.forRoot({ /* ... */ }),
    11
    BrowserAnimationsModule
    12
    ),
    13
    ],
    14
    };
    15

    View Slide

  44. View Slide

  45. View Slide

  46. @Injectable()
    export class MyInterceptor implements HttpInterceptor {
    constructor(private authService: AuthService){}
    intercept(request: HttpRequest>, next: HttpHandler): Observable {
    const accessToken = this.authService.getToken();
    const clonedRequest = req.clone({
    headers: req.headers.set('Authorization', `Bearer ${accessToken}`),
    });
    return next.handle(clonedRequest);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    View Slide

  47. export function myInterceptor(req: HttpRequest, next: HttpHandlerFn) {
    const loadingService = inject(AuthService);
    const accessToken = this.authService.getToken();
    const clonedRequest = req.clone({
    headers: req.headers.set('Authorization', `Bearer ${accessToken}`),
    });
    return next(clonedRequest);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  48. providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
    ],
    1
    2
    3

    View Slide

  49. providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
    ],
    1
    2
    3
    providers: [
    provideHttpClient(withInterceptors([authInterceptor()])),
    ],
    1
    2
    3

    View Slide

  50. @Injectable({ providedIn: 'root' })
    export class AuthorizationGuard implements CanActivate {
    constructor(
    private oidcSecurityService: OidcSecurityService,
    private router: Router
    ) {}
    canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
    ): Observable {
    return this.oidcSecurityService.isAuthenticated$.pipe(
    map(({ isAuthenticated }) => {
    // allow navigation if authenticated
    if (isAuthenticated) {
    return true;
    }
    // redirect if not authenticated
    return this.router.parseUrl('');
    })
    );
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    View Slide

  51. export const authenticatedGuard = () => {
    const router = inject(Router);
    const securityService = inject(OidcSecurityService);
    return securityService.isAuthenticated$.pipe(
    map(({ isAuthenticated }) => {
    // allow navigation if authenticated
    if (isAuthenticated) {
    return true;
    }
    // redirect if not authenticated
    return router.parseUrl('');
    })
    );
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    View Slide

  52. export const DOGGOS_ROUTES: Routes = [
    {
    path: 'my',
    component: MyDoggosComponent,
    canActivate: [isAuthenticated],
    },
    {
    path: 'my/add',
    component: AddDoggoComponent,
    canActivate: [isAuthenticated],
    },
    ];
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    View Slide

  53. export const DOGGOS_ROUTES: Routes = [
    {
    path: 'my',
    component: MyDoggosComponent,
    canActivate: [isAuthenticated],
    },
    {
    path: 'my/add',
    component: AddDoggoComponent,
    canActivate: [isAuthenticated],
    },
    ];
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    canActivate: [isAuthenticated],
    canActivate: [isAuthenticated],
    export const DOGGOS_ROUTES: Routes = [
    1
    {
    2
    path: 'my',
    3
    component: MyDoggosComponent,
    4
    5
    },
    6
    {
    7
    path: 'my/add',
    8
    component: AddDoggoComponent,
    9
    10
    },
    11
    ];
    12

    View Slide

  54. View Slide

  55. View Slide

  56. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    View Slide

  57. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    firstName = signal('Jane');
    lastName = signal('Doe');
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21

    View Slide

  58. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    firstName = signal('Jane');
    lastName = signal('Doe');
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21

    View Slide

  59. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    firstName = signal('Jane');
    lastName = signal('Doe');
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    {{ fullName() }}
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21

    View Slide

  60. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    firstName = signal('Jane');
    lastName = signal('Doe');
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    {{ fullName() }}
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    effect(() => console.log('Name changed:', this.fullName()));
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21

    View Slide

  61. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    firstName = signal('Jane');
    lastName = signal('Doe');
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    {{ fullName() }}
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    effect(() => console.log('Name changed:', this.fullName()));
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    this.firstName.set(newName);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    19
    }
    20
    }
    21

    View Slide

  62. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    firstName = signal('Jane');
    lastName = signal('Doe');
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    {{ fullName() }}
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    effect(() => console.log('Name changed:', this.fullName()));
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    this.firstName.set(newName);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    19
    }
    20
    }
    21
    firstName = signal('Jane');
    this.firstName.set(newName);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    19
    }
    20
    }
    21

    View Slide

  63. @Component({
    selector: 'my-app',
    standalone: true,
    template: `
    {{ fullName() }}
    Click
    `,
    })
    export class App {
    firstName = signal('Jane');
    lastName = signal('Doe');
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
    }
    setName(newName: string) {
    this.firstName.set(newName);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    firstName = signal('Jane');
    lastName = signal('Doe');
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    {{ fullName() }}
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    effect(() => console.log('Name changed:', this.fullName()));
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21
    this.firstName.set(newName);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    19
    }
    20
    }
    21
    firstName = signal('Jane');
    this.firstName.set(newName);
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    effect(() => console.log('Name changed:', this.fullName()));
    15
    }
    16
    17
    setName(newName: string) {
    18
    19
    }
    20
    }
    21
    effect(() => console.log('Name changed:', this.fullName()));
    @Component({
    1
    selector: 'my-app',
    2
    standalone: true,
    3
    template: `
    4
    {{ fullName() }}
    5
    Click
    6
    `,
    7
    })
    8
    export class App {
    9
    firstName = signal('Jane');
    10
    lastName = signal('Doe');
    11
    fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
    12
    13
    constructor() {
    14
    15
    }
    16
    17
    setName(newName: string) {
    18
    this.firstName.set(newName);
    19
    }
    20
    }
    21

    View Slide

  64. import { toObservable } from '@angular/core/rxjs-interop';
    @Component({...})
    export class App {
    count = signal(0);
    count$ = toObservable(this.count);
    ngOnInit() {
    this.count$.subscribe(() => ...);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    View Slide

  65. import { toSignal } from '@angular/core/rxjs-interop';
    @Component({
    template: `
    {{ row }}
    `
    })
    export class App {
    dataService = inject(DataService);
    data = toSignal(this.dataService.data$, []);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    View Slide

  66. View Slide

  67. View Slide

  68. Jest in Angular CLI
    {
    "projects": {
    "my-app": {
    "architect": {
    "test": {
    "builder": "@angular-devkit/build-angular:jest",
    "options": {
    "tsConfig": "tsconfig.spec.json",
    "polyfills": ["zone.js", "zone.js/testing"]
    }
    }
    }
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    View Slide

  69. Autocomplete imports in
    templates

    View Slide

  70. Required Inputs
    @Component(...)
    export class App {
    @Input({ required: true }) title: string = '';
    }
    1
    2
    3
    4

    View Slide

  71. Injectable Destroy
    import { Injectable, DestroyRef } from '@angular/core';
    @Injectable(...)
    export class AppService {
    destroyRef = inject(DestroyRef);
    destroy() {
    this.destroyRef.onDestroy(() => /* cleanup */ );
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  72. Self Closing Tags
    1

    1

    View Slide

  73. New Template Syntax
    trackByFunction(index, item) {
    return item.id;
    }

    Item #{{ idx }}: {{ item.name }}

    1
    2
    3
    4
    5
    6
    7

    View Slide

  74. New Template Syntax
    trackByFunction(index, item) {
    return item.id;
    }

    Item #{{ idx }}: {{ item.name }}

    1
    2
    3
    4
    5
    6
    7
    {#for item of items; track item.id; let idx = $index, e = $even}
    Item #{{ idx }}: {{ item.name }}
    {/for}
    1
    2
    3

    View Slide

  75. New Template Syntax
    {#for item of items; track item.id}
    {{ item }}
    {:empty}
    There were no items in the list.
    {/for}
    1
    2
    3
    4
    5

    View Slide

  76. New Template Syntax

    Main case was true!



    Extra case was true!



    False case!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    View Slide

  77. New Template Syntax
    {#if cond.expr}
    Main case was true!
    {:else if other.expr}
    Extra case was true!
    {:else}
    False case!
    {/if}
    1
    2
    3
    4
    5
    6
    7

    View Slide

  78. View Slide

  79. View Slide

  80. View Slide

  81. @Component({
    selector: 'app-my-doggos',
    standalone: true,
    templateUrl: './my-doggos.component.html',
    styleUrls: ['./my-doggos.component.css'],
    imports: [AsyncPipe, RouterLink, DatePipe, DecimalPipe, NgFor],
    })
    export class MyDoggosComponent implements OnInit {
    private readonly store = inject(Store);
    doggos = this.store.select(getMyDoggos);
    ngOnInit(): void {
    this.store.dispatch(DoggosActions.loadMyDoggos());
    }
    deleteDoggo(doggo: Doggo) {
    this.store.dispatch(DoggosActions.deleteDoggo({ doggo }));
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    View Slide

  82. Component
    Store
    State
    Reducer
    Effects
    Actions

    View Slide

  83. View Slide

  84. View Slide

  85. View Slide

  86. View Slide

  87. View Slide

  88. View Slide

  89. View Slide

  90. Share code between apps

    View Slide

  91. Share code between apps
    Provide feature to one app

    View Slide

  92. Share code between apps
    Provide feature to one app
    Multiple Libs = 1 Feature

    View Slide

  93. Feature
    Ui
    Feature
    Domain Utils
    Util-
    Environments
    Util-Auth UI-Common Util-Common
    About Doggos
    App
    Shared

    View Slide

  94. type:app, scope:doggo-rating-app

    View Slide

  95. type:feature
    scope:about
    type:app, scope:doggo-rating-app

    View Slide

  96. type:feature
    type:ui
    type:feature
    type:domain type:utils
    scope:about scope:doggos
    type:app, scope:doggo-rating-app

    View Slide

  97. type:feature
    type:ui
    type:feature
    type:domain type:utils
    scope:about scope:doggos
    type:app, scope:doggo-rating-app
    scope:shared
    type:util type:util type:ui type:util

    View Slide

  98. View Slide

  99. {
    "name": "about-feature",
    "$schema": "../node_modules/nx/schemas/project-schema.json",
    "projectType": "library",
    "sourceRoot": "libs/about/feature/src",
    "prefix": "ps-doggo-rating",
    "targets": {
    // ...
    },
    "tags": ["type:feature", "scope:about"]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    View Slide

  100. {
    "name": "about-feature",
    "$schema": "../node_modules/nx/schemas/project-schema.json",
    "projectType": "library",
    "sourceRoot": "libs/about/feature/src",
    "prefix": "ps-doggo-rating",
    "targets": {
    // ...
    },
    "tags": ["type:feature", "scope:about"]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    "tags": ["type:feature", "scope:about"]
    {
    1
    "name": "about-feature",
    2
    "$schema": "../node_modules/nx/schemas/project-schema.json",
    3
    "projectType": "library",
    4
    "sourceRoot": "libs/about/feature/src",
    5
    "prefix": "ps-doggo-rating",
    6
    "targets": {
    7
    // ...
    8
    },
    9
    10
    }
    11

    View Slide

  101. "@nx/enforce-module-boundaries": [
    "error",
    {
    "enforceBuildableLibDependency": true,
    "allow": [],
    "depConstraints": [
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    {
    "sourceTag": "scope:doggos",
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    View Slide

  102. "@nx/enforce-module-boundaries": [
    "error",
    {
    "enforceBuildableLibDependency": true,
    "allow": [],
    "depConstraints": [
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    {
    "sourceTag": "scope:doggos",
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21

    View Slide

  103. "@nx/enforce-module-boundaries": [
    "error",
    {
    "enforceBuildableLibDependency": true,
    "allow": [],
    "depConstraints": [
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    {
    "sourceTag": "scope:doggos",
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    "depConstraints": [
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21

    View Slide

  104. "@nx/enforce-module-boundaries": [
    "error",
    {
    "enforceBuildableLibDependency": true,
    "allow": [],
    "depConstraints": [
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    {
    "sourceTag": "scope:doggos",
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    "depConstraints": [
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos", "scope:shared"]
    21

    View Slide

  105. "@nx/enforce-module-boundaries": [
    "error",
    {
    "enforceBuildableLibDependency": true,
    "allow": [],
    "depConstraints": [
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    {
    "sourceTag": "scope:doggos",
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    "depConstraints": [
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    [],
    "depConstraints": [
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    15
    16
    17
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos", "scope:shared"]
    21
    },
    22
    {
    23
    "sourceTag": "scope:shared",
    24
    "onlyDependOnLibsWithTags": ["scope:shared"]
    25
    },
    26
    {
    27

    View Slide

  106. "@nx/enforce-module-boundaries": [
    "error",
    {
    "enforceBuildableLibDependency": true,
    "allow": [],
    "depConstraints": [
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    {
    "sourceTag": "scope:doggos",
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    "depConstraints": [
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    {
    "sourceTag": "scope:doggo-rating-app",
    "onlyDependOnLibsWithTags": [
    "scope:doggos",
    "scope:about",
    "scope:shared"
    ]
    },
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    {
    "sourceTag": "scope:about",
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    },
    "@nx/enforce-module-boundaries": [
    1
    "error",
    2
    {
    3
    "enforceBuildableLibDependency": true,
    4
    "allow": [],
    5
    "depConstraints": [
    6
    {
    7
    "sourceTag": "scope:doggo-rating-app",
    8
    "onlyDependOnLibsWithTags": [
    9
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    15
    16
    17
    18
    {
    19
    "sourceTag": "scope:doggos",
    20
    "onlyDependOnLibsWithTags": ["scope:doggos" "scope:shared"]
    21
    {
    "sourceTag": "scope:doggos",
    "onlyDependOnLibsWithTags": ["scope:doggos", "scope:shared"]
    },
    y p g [
    "scope:doggos",
    10
    "scope:about",
    11
    "scope:shared"
    12
    ]
    13
    },
    14
    {
    15
    "sourceTag": "scope:about",
    16
    "onlyDependOnLibsWithTags": ["scope:about", "scope:shared"]
    17
    },
    18
    19
    20
    21
    22
    {
    23
    "sourceTag": "scope:shared",
    24
    "onlyDependOnLibsWithTags": ["scope:shared"]
    25
    },
    26
    {
    27
    "sourceTag": "type:app",
    28
    "onlyDependOnLibsWithTags": [
    29
    "type:feature",
    30
    "type:util",
    31

    View Slide

  107. View Slide

  108. https://go.nx.dev/angular-enterprise-monorepo-patterns-new-book

    View Slide

  109. View Slide

  110. @Component({
    selector: 'app-my-doggos',
    standalone: true,
    templateUrl: './my-doggos.component.html',
    styleUrls: ['./my-doggos.component.css'],
    imports: [AsyncPipe, RouterLink, DatePipe, DecimalPipe, NgFor],
    })
    export class MyDoggosComponent implements OnInit {
    private readonly store = inject(Store);
    doggos = this.store.select(getMyDoggos);
    ngOnInit(): void {
    this.store.dispatch(DoggosActions.loadMyDoggos());
    }
    deleteDoggo(doggo: Doggo) {
    this.store.dispatch(DoggosActions.deleteDoggo({ doggo }));
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    View Slide

  111. @Component({
    selector: 'app-my-doggos',
    standalone: true,
    templateUrl: './my-doggos.component.html',
    styleUrls: ['./my-doggos.component.css'],
    imports: [AsyncPipe, RouterLink, DatePipe, DecimalPipe, NgFor],
    })
    export class MyDoggosComponent implements OnInit {
    private readonly store = inject(Store);
    doggos = this.store.select(getMyDoggos);
    ngOnInit(): void {
    this.store.dispatch(DoggosActions.loadMyDoggos());
    }
    deleteDoggo(doggo: Doggo) {
    this.store.dispatch(DoggosActions.deleteDoggo({ doggo }));
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    doggos = this.store.select(getMyDoggos);
    @Component({
    1
    selector: 'app-my-doggos',
    2
    standalone: true,
    3
    templateUrl: './my-doggos.component.html',
    4
    styleUrls: ['./my-doggos.component.css'],
    5
    imports: [AsyncPipe, RouterLink, DatePipe, DecimalPipe, NgFor],
    6
    })
    7
    export class MyDoggosComponent implements OnInit {
    8
    private readonly store = inject(Store);
    9
    10
    11
    12
    ngOnInit(): void {
    13
    this.store.dispatch(DoggosActions.loadMyDoggos());
    14
    }
    15
    16
    deleteDoggo(doggo: Doggo) {
    17
    this.store.dispatch(DoggosActions.deleteDoggo({ doggo }));
    18
    }
    19
    }
    20

    View Slide

  112. doggos = this.store.selectSignal(getMyDoggos);
    @Component({
    1
    selector: 'app-my-doggos',
    2
    standalone: true,
    3
    templateUrl: './my-doggos.component.html',
    4
    styleUrls: ['./my-doggos.component.css'],
    5
    imports: [AsyncPipe, RouterLink, DatePipe, DecimalPipe, NgFor],
    6
    })
    7
    export class MyDoggosComponent implements OnInit {
    8
    private readonly store = inject(Store);
    9
    10
    11
    12
    ngOnInit(): void {
    13
    this.store.dispatch(DoggosActions.loadMyDoggos());
    14
    }
    15
    16
    deleteDoggo(doggo: Doggo) {
    17
    this.store.dispatch(DoggosActions.deleteDoggo({ doggo }));
    18
    }
    19
    }
    20

    View Slide

  113. View Slide

  114. View Slide

  115. View Slide

  116. View Slide

  117. View Slide

  118. View Slide