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

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. 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
  2. import { Component } from '@angular/core'; @Component({ selector: 'app-root', template:

    `<span>{{ title }}</span>`, styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'oldangularapp'; } 1 2 3 4 5 6 7 8 9 10
  3. import { bootstrapApplication } from '@angular/platform-browser'; import { ImageGridComponent }

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

    from'./image-grid'; @Component({ standalone: true, selector: 'photo-gallery', imports: [ImageGridComponent], template: ` <image-grid [images]="imageList"></image-grid> `, }) 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 <image-grid [images]="imageList"></image-grid> 9 `, 10 }) 11 export class PhotoGalleryComponent { 12 // component logic 13 } 14 15 bootstrapApplication(PhotoGalleryComponent); 16
  5. import { bootstrapApplication } from '@angular/platform-browser'; import { ImageGridComponent }

    from'./image-grid'; @Component({ standalone: true, selector: 'photo-gallery', imports: [ImageGridComponent], template: ` <image-grid [images]="imageList"></image-grid> `, }) 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 <image-grid [images]="imageList"></image-grid> 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 <image-grid [images]="imageList"></image-grid> 9 `, 10 }) 11 export class PhotoGalleryComponent { 12 // component logic 13 } 14 15 bootstrapApplication(PhotoGalleryComponent); 16
  6. import { bootstrapApplication } from '@angular/platform-browser'; import { ImageGridComponent }

    from'./image-grid'; @Component({ standalone: true, selector: 'photo-gallery', imports: [ImageGridComponent], template: ` <image-grid [images]="imageList"></image-grid> `, }) 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 <image-grid [images]="imageList"></image-grid> 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 <image-grid [images]="imageList"></image-grid> 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 <image-grid [images]="imageList"></image-grid> 9 `, 10 }) 11 12 13 14 15 bootstrapApplication(PhotoGalleryComponent); 16
  7. import { bootstrapApplication } from '@angular/platform-browser'; import { ImageGridComponent }

    from'./image-grid'; @Component({ standalone: true, selector: 'photo-gallery', imports: [ImageGridComponent], template: ` <image-grid [images]="imageList"></image-grid> `, }) 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 <image-grid [images]="imageList"></image-grid> 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 <image-grid [images]="imageList"></image-grid> 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 <image-grid [images]="imageList"></image-grid> 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 <image-grid [images]="imageList"></image-grid> 9 `, 10 }) 11 export class PhotoGalleryComponent { 12 // component logic 13 } 14 15 16
  8. Convert all components, directives, and pipes to standalone Remove unnecessary

    NgModule classes Bootstrap the application using standalone APIs
  9. export const appConfig: ApplicationConfig = { providers: [ // ...

    importProvidersFrom( ToastrModule.forRoot({ /* ... */ }), BrowserAnimationsModule ), ], }; 1 2 3 4 5 6 7 8 9
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. @Injectable() export class MyInterceptor implements HttpInterceptor { constructor(private authService: AuthService){}

    intercept(request: HttpRequest<any>>, next: HttpHandler): Observable<HttpEvent<any> { 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
  16. export function myInterceptor(req: HttpRequest<unknown>, 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
  17. providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },

    ], 1 2 3 providers: [ provideHttpClient(withInterceptors([authInterceptor()])), ], 1 2 3
  18. @Injectable({ providedIn: 'root' }) export class AuthorizationGuard implements CanActivate {

    constructor( private oidcSecurityService: OidcSecurityService, private router: Router ) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean | UrlTree> { 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
  19. 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
  20. 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
  21. 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
  22. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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
  23. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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 <button (click)="setName('John')">Click</button> 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
  24. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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
  25. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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
  26. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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
  27. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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
  28. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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
  29. @Component({ selector: 'my-app', standalone: true, template: ` {{ fullName() }}

    <button (click)="setName('John')">Click</button> `, }) 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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 <button (click)="setName('John')">Click</button> 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
  30. 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
  31. import { toSignal } from '@angular/core/rxjs-interop'; @Component({ template: ` <li

    *ngFor="let row of data()"> {{ row }} </li> ` }) export class App { dataService = inject(DataService); data = toSignal(this.dataService.data$, []); } 1 2 3 4 5 6 7 8 9 10 11 12
  32. 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
  33. 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
  34. New Template Syntax trackByFunction(index, item) { return item.id; } <div

    *ngFor="let item of items; index as idx; trackBy: trackByFunction"> Item #{{ idx }}: {{ item.name }} </div> 1 2 3 4 5 6 7
  35. New Template Syntax trackByFunction(index, item) { return item.id; } <div

    *ngFor="let item of items; index as idx; trackBy: trackByFunction"> Item #{{ idx }}: {{ item.name }} </div> 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
  36. 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
  37. New Template Syntax <ng-container *ngIf="cond.expr; else elseBlock"> Main case was

    true! </ng-container> <ng-template #elseBlock> <ng-container *ngIf="other.expr; else finalElseBlock"> Extra case was true! </ng-container> </ng-template> <ng-template #finalElseBlock> False case! </ng-template> 1 2 3 4 5 6 7 8 9 10 11 12 13
  38. 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
  39. @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
  40. { "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
  41. { "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
  42. "@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
  43. "@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
  44. "@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
  45. "@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
  46. "@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
  47. "@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
  48. @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
  49. @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
  50. 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