Share
Modern Angular Apps in 2026
Angular has changed significantly in recent years—signals, zoneless change detection, standalone components, typed forms, new router APIs, the signal store, and much more have revolutionized the way we build Angular applications.
But what will a modern Angular app really look like in 2026? Which concepts are “best practice” today – and which belong in the past?
In this talk, we'll take a look at the latest features of the Angular ecosystem and show how they work together to develop high-performance, scalable, and maintainable applications.
Using practical examples, you'll learn:
- How modern architectural approaches work with routes, standalone components, and feature modules
- How signals and the signal store simplify state management
- How to get the most out of your codebase with the new Angular tools, Nx, and TypeScript
Whether you want to modernize existing projects or build new applications that are future-proof, after this talk you will know what modern Angular apps will look like in 2026.
Fabian Gosebrink
Transcript
147 slides
-
export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing:
true }), provideRouter( routes, withComponentInputBinding(), withViewTransitions() ), provideHttpClient(), provideToastr({ positionClass: 'toast-bottom-right' }), // ... ], }; 1 2 3 4 5 6 7 8 9 10 11 12 13 -
export const appConfig: ApplicationConfig = { providers: [ provideRouter( routes,
withComponentInputBinding(), withViewTransitions() ), provideToastr({ positionClass: 'toast-bottom-right' }), // ... ], }; 1 2 3 4 5 6 7 8 9 10 11 -
@for (item of items(); track item.name) { <li> {{ item.name
}}</li> } @empty { <li> There are no items. </li> } 1 2 3 4 5 -
@if (a > b) { {{a}} is greater than {{b}}
} @else if (b > a) { {{a}} is less than {{b}} } @else { {{a}} is equal to {{b}} } 1 2 3 4 5 6 7 -
$count Number of items in a collection iterated over $index
Index of the current row $first Whether the current row is the first row $last Whether the current row is the last row $even Whether the current row index is even $odd Whether the current row index is odd 1 2 3 4 5 6 -
deferrable views @defer (on viewport) { <heavy-component /> } @placeholder
{ <div>Loading...</div> } 1 2 3 4 5 -
@Component({ // ... }) export class ProductComponent { product =
input.required<Product>(); clicked = output<string>(); cartClicked = output<Product>(); } 1 2 3 4 5 6 7 8 9 -
functional guards export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService); return auth.isAuthenticated(); }; 1 2 3 4 5 -
input binding // routes { path: 'user/:id', component: UserDetail }
// app.config.ts provideRouter(routes, withComponentInputBinding()) // Component @Component({...}) export class UserDetail { id = input(''); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -
@Component({ selector: 'lib-dog-detail', imports: [RouterLink, NgOptimizedImage, DatePipe, DecimalPipe], providers: [...],
templateUrl: './dog-detail.component.html', styleUrls: ['./dog-detail.component.scss'], }) export class DogDetailComponent { // ... } 1 2 3 4 5 6 7 8 9 10 -
@Component({ selector: 'lib-main-dog', templateUrl: './main-dog.component.html', styleUrls: ['./main-dog.component.scss'], providers: [...], imports:
[DogListComponent, DogRateComponent], }) export class MainDogComponent { // ... } 1 2 3 4 5 6 7 8 9 10 -
feature-based src/ ├── features/ │ ├── user/ │ ├── products/
│ └── dashboard/ └── shared/ ├── .../ 1 2 3 4 5 6 7 8 -
feature structure feature └── user ├── container ├── presentational ├──
domain │ ├── state │ ├── models │ └── ... └── utils 1 2 3 4 5 6 7 8 9 -
{ path: 'dogs', loadChildren: () => import('@dog-rating/dogs/feature').then((m) => m.DOGS_ROUTES), },
{ path: 'about', loadChildren: () => import('@dog-rating/about/feature').then((m) => m.ABOUT_ROUTES), }, { path: '**', pathMatch: 'full', redirectTo: 'dogs', }, export const APP_ROUTES: Routes = [ 1 { 2 path: '', 3 component: LayoutComponent, 4 children: [ 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ], 21 }, 22 ]; 23 -
children: [ { path: 'products', loadComponent: () => import( './features/products/container/products/products.component'
).then((c) => c.ProductsComponent), }, { path: 'products/:id', loadComponent: () => import( './features/product-detail/container/product-detail/product-detail.component' ).then((c) => c.ProductDetailComponent), }, { path: 'checkout', loadComponent: () => import( './features/checkout/container/checkout/checkout.component' ).then((c) => c.CheckoutComponent), }, { path: '**', pathMatch: 'full', redirectTo: 'products', }, ], export const routes: Routes = [ 1 { 2 path: '', 3 component: ShellComponent, 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 }, 33 ]; 34 -
@Component({ selector: 'lib-single-dog', imports: [RouterLink, DecimalPipe, DatePipe], templateUrl: './single-dog.component.html', styleUrls:
['./single-dog.component.scss'], }) export class SingleDogComponent { dog = input<Dog | null>(); dogDeleted = output<Dog>(); } 1 2 3 4 5 6 7 8 9 10 11 -
container manage the data react to child events entry point
for feature # less than presentationals -
@Component({ selector: 'lib-main-dog', templateUrl: './main-dog.component.html', styleUrls: ['./main-dog.component.scss'], providers: [MainDogStore], imports:
[DogListComponent, DogRateComponent], }) export class MainDogComponent { dogId = input(''); store = inject(MainDogStore); constructor() { this.store.selectDog(this.dogId); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -
nearly no business logic in components i am serious ...
we will talk about this later -
todos = signal<Todo[]>([]); count = computed(() => todos().length); effect(() =>
{ console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 -
todos = signal<Todo[]>([]); count = computed(() => todos().length); effect(() =>
{ console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 count = computed(() => todos().length); todos = signal<Todo[]>([]); 1 2 3 4 effect(() => { 5 console.log('Todos count changed to ', count()); 6 }); 7 -
todos = signal<Todo[]>([]); count = computed(() => todos().length); effect(() =>
{ console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 count = computed(() => todos().length); todos = signal<Todo[]>([]); 1 2 3 4 effect(() => { 5 console.log('Todos count changed to ', count()); 6 }); 7 effect(() => { console.log('Todos count changed to ', count()); }); todos = signal<Todo[]>([]); 1 2 count = computed(() => todos().length); 3 4 5 6 7 -
todos = signal<Todo[]>([]); console.log('Todos: ' + todos()); // Reactive in
TS effect(() => { console.log('Todos', todos()); }); // Reactive in HTML <div>{{ todos() }}</div> 1 2 3 4 5 6 7 8 9 10 11 -
todos = signal<Todo[]>([]); console.log('Todos: ' + todos()); // Reactive in
TS effect(() => { console.log('Todos', todos()); }); // Reactive in HTML <div>{{ todos() }}</div> 1 2 3 4 5 6 7 8 9 10 11 // Reactive in TS effect(() => { console.log('Todos', todos()); }); // Reactive in HTML <div>{{ todos() }}</div> todos = signal<Todo[]>([]); 1 2 console.log('Todos: ' + todos()); 3 4 5 6 7 8 9 10 11 -
todos = signal<Todo[]>([]); count = computed(() => todos().length); effect(() =>
{ console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 -
todos = signal<Todo[]>([]); count = computed(() => todos().length); effect(() =>
{ console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 count = computed(() => todos().length); todos = signal<Todo[]>([]); 1 2 3 4 effect(() => { 5 console.log('Todos count changed to ', count()); 6 }); 7 -
todos = signal<Todo[]>([]); count = computed(() => todos().length); effect(() =>
{ console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 count = computed(() => todos().length); todos = signal<Todo[]>([]); 1 2 3 4 effect(() => { 5 console.log('Todos count changed to ', count()); 6 }); 7 effect(() => { console.log('Todos count changed to ', count()); }); todos = signal<Todo[]>([]); 1 2 count = computed(() => todos().length); 3 4 5 6 7 -
Signal todos() Reactive Context effect() / HTML computed() calc version++
pull value version++ -
Signal todos() Reactive Context effect() / HTML computed() calc version++
pull value calc version++ -
Signal todos() Reactive Context effect() / HTML computed() calc version++
pull value calc version++ pull value -
@Component({ selector: 'app-root', template: ` <button (click)="increaseCounter()"> Increase counter </button>
{{ counter() }} `, }) export class App { counter = signal(0); increaseCounter(){ this.counter.update((counter) => counter+1) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 -
@Component({ selector: 'app-root', template: ` <button (click)="increaseCounter()"> Increase counter </button>
{{ counter() }} `, }) export class App { counter = signal(0); increaseCounter(){ this.counter.update((counter) => counter+1) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 {{ counter() }} counter = signal(0); @Component({ 1 selector: 'app-root', 2 template: ` 3 <button (click)="increaseCounter()"> 4 Increase counter 5 </button> 6 7 `, 8 }) 9 export class App { 10 11 12 increaseCounter(){ 13 this.counter.update((counter) => counter+1) 14 } 15 } 16 -
export class TodoMainComponent { todos: Todo[] = []; addTodo(todo: Todo)
{ this.todos.push(todo); } deleteTodo(todoId: number) { this.todos = this.todos.filter(...); } } 1 2 3 4 5 6 7 8 9 10 11 -
src/ └── app/ ├── features/ │ ├── dashboard/ │ │
└── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10 -
export class ProductsComponent implements OnInit { products = signal<{ category:
string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {}, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 -
export class ProductsComponent implements OnInit { products = signal<{ category:
string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {}, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 products = signal<{ category: string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); export class ProductsComponent implements OnInit { 1 2 3 4 5 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {}, 22 -
export class ProductsComponent implements OnInit { products = signal<{ category:
string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {}, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 products = signal<{ category: string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); export class ProductsComponent implements OnInit { 1 2 3 4 5 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {}, 22 this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {}, ); const groupedByCategory = Object.keys(categoryProductMap).map( (category) => ({ category, products: categoryProductMap[category], }), ); 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 -
export class ProductsComponent implements OnInit { products = signal<{ category:
string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {}, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 products = signal<{ category: string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); export class ProductsComponent implements OnInit { 1 2 3 4 5 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {}, 22 this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {}, export class ProductsComponent implements OnInit { 1 products = signal<{ category: string; products: Product[] }[]>([]); 2 private readonly productsService = inject(ProductsService); 3 private readonly checkoutService = inject(CheckoutService); 4 private readonly toastrService = inject(ToastrService); 5 private readonly router = inject(Router); 6 7 ngOnInit() { 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 onProductClicked(id: string): void { this.router.navigate(['products', id]); } onCartClicked(product: Product): void { this.checkoutService.addToCart(product).subscribe(() => { this.toastrService.success('Item Added to Cart'); }); } ); 23 24 const groupedByCategory = Object.keys(categoryProductMap).map( 25 (category) => ({ 26 category, 27 products: categoryProductMap[category], 28 }), 29 ); 30 31 this.products.set(groupedByCategory); 32 }); 33 } 34 35 36 37 38 39 40 41 42 43 44 -
ngrx classic export class ProductsComponent implements OnInit { private readonly
store = inject(Store); readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); ngOnInit(): void { this.store.dispatch(ProductsActions.loadProducts()); } onProductClicked(id: string): void { this.store.dispatch(ProductsActions.navigateToDetail({ id })); } onCartClicked(product: Product): void { this.store.dispatch(CheckoutActions.addProduct({ product })); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 -
ngrx classic export class ProductsComponent implements OnInit { private readonly
store = inject(Store); readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); ngOnInit(): void { this.store.dispatch(ProductsActions.loadProducts()); } onProductClicked(id: string): void { this.store.dispatch(ProductsActions.navigateToDetail({ id })); } onCartClicked(product: Product): void { this.store.dispatch(CheckoutActions.addProduct({ product })); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); this.store.dispatch(ProductsActions.loadProducts()); this.store.dispatch(ProductsActions.navigateToDetail({ id })); this.store.dispatch(CheckoutActions.addProduct({ product })); export class ProductsComponent implements OnInit { 1 private readonly store = inject(Store); 2 3 4 5 6 7 ngOnInit(): void { 8 9 } 10 11 onProductClicked(id: string): void { 12 13 } 14 15 onCartClicked(product: Product): void { 16 17 } 18 } 19 -
ngrx signal store export class ProductsComponent implements OnInit { private
readonly store = inject(ProductsStore); readonly productsByCategories = this.store.productsByCategories(); ngOnInit(): void { this.store.loadProducts(); } onProductClicked(id: string): void { this.store.navigateToDetail({ id }); } onCartClicked(product: Product): void { this.store.addProduct({ product }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -
export const BookingStore = signalStore( //... withState(...), withComputed(...), withMethods((store, service
= inject(...)) => ({ loadBooking: rxMethod<number>( exhaustMap((id) => service.load(id).pipe( tapResponse({ next: (booking) => patchState(store, { booking }), error: (err) => console.error(err) }) )) }), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -
export const BookingStore = signalStore( // ... withState(...), withComputed(...), withMethods((store,
service = ...) => ({ async loadBooking(id): Promise<number> { const booking = await service.load(id); patchState(store, { booking }); }, })), ); 1 2 3 4 5 6 7 8 9 10 11 -
nx benefits nx affected:test nx affected:build nx graph nx run-many
--target=test 1 2 3 4 -
module boundaries rules: { '@nx/enforce-module-boundaries': [ 'error', { enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'], depConstraints: [ { sourceTag: 'scope:dog-rating-app', onlyDependOnLibsWithTags: [ 'scope:dogs', 'scope:about', 'scope:shared', ], }, { sourceTag: 'scope:about', onlyDependOnLibsWithTags: ['scope:about', 'scope:shared'], }, { sourceTag: 'scope:dogs', onlyDependOnLibsWithTags: ['scope:dogs', 'scope:shared'], }, // ... ], }, ], }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 -
module boundaries rules: { '@nx/enforce-module-boundaries': [ 'error', { enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'], depConstraints: [ { sourceTag: 'scope:dog-rating-app', onlyDependOnLibsWithTags: [ 'scope:dogs', 'scope:about', 'scope:shared', ], }, { sourceTag: 'scope:about', onlyDependOnLibsWithTags: ['scope:about', 'scope:shared'], }, { sourceTag: 'scope:dogs', onlyDependOnLibsWithTags: ['scope:dogs', 'scope:shared'], }, // ... ], }, ], }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 sourceTag: 'scope:dog-rating-app', onlyDependOnLibsWithTags: [ 'scope:dogs', 'scope:about', 'scope:shared', ], rules: { 1 '@nx/enforce-module-boundaries': [ 2 'error', 3 { 4 enforceBuildableLibDependency: true, 5 allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'], 6 depConstraints: [ 7 { 8 9 10 11 12 13 14 }, 15 { 16 sourceTag: 'scope:about', 17 onlyDependOnLibsWithTags: ['scope:about', 'scope:shared'], 18 }, 19 { 20 sourceTag: 'scope:dogs', 21 onlyDependOnLibsWithTags: ['scope:dogs', 'scope:shared'], 22 }, 23 // ... 24 ], 25 }, 26 ], 27 }, 28 -
module boundaries rules: { '@nx/enforce-module-boundaries': [ 'error', { enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'], depConstraints: [ { sourceTag: 'scope:dog-rating-app', onlyDependOnLibsWithTags: [ 'scope:dogs', 'scope:about', 'scope:shared', ], }, { sourceTag: 'scope:about', onlyDependOnLibsWithTags: ['scope:about', 'scope:shared'], }, { sourceTag: 'scope:dogs', onlyDependOnLibsWithTags: ['scope:dogs', 'scope:shared'], }, // ... ], }, ], }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 sourceTag: 'scope:dog-rating-app', onlyDependOnLibsWithTags: [ 'scope:dogs', 'scope:about', 'scope:shared', ], rules: { 1 '@nx/enforce-module-boundaries': [ 2 'error', 3 { 4 enforceBuildableLibDependency: true, 5 allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'], 6 depConstraints: [ 7 { 8 9 10 11 12 13 14 }, 15 { 16 sourceTag: 'scope:about', 17 onlyDependOnLibsWithTags: ['scope:about', 'scope:shared'], 18 }, 19 { 20 sourceTag: 'scope:dogs', 21 onlyDependOnLibsWithTags: ['scope:dogs', 'scope:shared'], 22 }, 23 // ... 24 ], 25 }, 26 ], 27 }, 28 { sourceTag: 'scope:about', onlyDependOnLibsWithTags: ['scope:about', 'scope:shared'], }, rules: { 1 '@nx/enforce-module-boundaries': [ 2 'error', 3 { 4 enforceBuildableLibDependency: true, 5 allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'], 6 depConstraints: [ 7 { 8 sourceTag: 'scope:dog-rating-app', 9 onlyDependOnLibsWithTags: [ 10 'scope:dogs', 11 'scope:about', 12 'scope:shared', 13 ], 14 }, 15 16 17 18 19 { 20 sourceTag: 'scope:dogs', 21 onlyDependOnLibsWithTags: ['scope:dogs', 'scope:shared'], 22 }, 23 // ... 24 ], 25 }, 26 ], 27 }, 28 -
state management give your data a place to live pattern
shapes architecture provides guidance -
resource api async data as signals auto loading states auto
error handling reactive parameters -
resource signals user.value() // The data user.status() // 'idle' |
'loading' | 'resolved' | 'error' user.isLoading() // Boolean user.hasValue() // Type guard 1 2 3 4 -
template usage @if(user.hasValue()) { <user-details [user]="user.value()" /> } @else if
(user.isLoading()) { <div>Loading...</div> } 1 2 3 4 5 -
reactive params sort = signal('asc'); users = httpResource<User[]>( () =>
`/users?sort=${this.sort()}` ); // Auto-reloads when sort changes! 1 2 3 4 5 6 7 -
basic form @Component({ selector: 'app-login', imports: [FormField], template: ` <input
type="email" [formField]="loginForm.email" /> <input type="password" [formField]="loginForm.password" /> ` }) export class LoginComponent { loginModel = signal({ email: '', password: '' }) loginForm = form(this.loginModel) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -
Do ✅ feature-thinking, route-drive, state-driven, ... ✅ standalone components +
lazy-loaded ✅ move to signals ✅ use rxjs, promises where it fits best ✅ think about your state ✅ manage your state ✅ think about using nx -
Don't ❌ inherit components ❌ neglect forms ❌ spread state
across the whole app ❌ make everything global state ❌ hide state in random services/subjects ❌ subscribe manually everywhere -
Name Link Fabian Gosebrink - Angular Signals under the Hood
| NG Belgrade Conf 2024 https://www.youtube.com/watch? v=8N_TDbZuF7M Ebook - Creating Modern Apps With Nx, Angular & Ngrx Signal Store https://offering.solutions/ebook-modern- angular-apps-ngrx/ Angular Deep Dive: Modern State with Signals and SignalStore https://app.pluralsight.com/ilx/video- courses/angular-deep-dive-modern-state- signals-signal-store/course-overview Angular Deep Dive: Monorepos with Nx https://app.pluralsight.com/ilx/video- courses/angular-deep-dive-monorepos- nx/course-overview Angular Signals vs RxJS – Answering the Questions Developers Ask https://offering.solutions/blog/articles/2025/ 06/14/angular-signals-vs-rxjs/