slides: bit.ly/3FSDwvf
We expect cooperation from all participants to help ensure a safe environment for everybody.
We treat everyone with respect, we refrain from using offensive language and imagery, and we encourage to report any derogatory or offensive behavior to a member of the JSLeague community.
We provide a fantastic environment for everyone to learn and share skills regardless of gender, gender identity and expression, age, sexual orientation, disability, physical appearance, body size, race, ethnicity, religion (or lack thereof), or technology choices.
We value your attendance and your participation in the JSLeague community and expect everyone to accord to the community Code of Conduct at all JSLeague workshops and other events.
Code of conduct
Whoami
Frontend Developer, Training Manager @ JsLeague
organizer for ngBucharest
@ngBucharest
groups/angularjs.bucharest
Overview
Advanced Angular
Advanced Angular
Agenda for the course
Advanced Angular
Schedule
Advanced Angular
@NgModule({
imports: [ CommonModule, Module1, Module2...],
declarations: [ AppComponent, Component1, Directive 1, ...],
...
})
export Class SomeModule {
}
@Component(...)
export Class AutocompleteComponent {
...
}
@NgModule({
imports: [
CommonModule,
...
],
declarations: [
AutocompleteComponent
],
exports: [AutocompleteComponent],
})
export Class AutocompleteModule {
...
}
ng generate component standalone --standalone (default true)
@Component({
selector: 'app-standalone',
standalone: true,
// all imports (modules and components) goes here
imports: [CommonModule, RouterOutlet, Component1],
templateUrl: './standalone.component.html',
styleUrls: ['./standalone.component.scss']
})
export class StandaloneComponent implements OnInit {
...
}
@NgModule({
imports: [
...,
// import library NgModule
WeatherWidgetModule,
...
]
})
export class DashboardComponent { }
@NgModule({
imports: [
...,
// import component directly
WeatherWidgetComponent,
SomeStandaloneDirective,
SomeStandalonePipe,
...
]
})
export class DashboardComponent { }
@Pipe ({
standalone: true,
name: 'countWords',
pure: true // the default
})
export class CountWordsPipe implements PipeTransform {
transform (value: string, format: string): string {[…]}
}
@Directive ({
standalone: true,
selector: 'img[errorFallback]',
providers: […]
})
export class ErrorFallbackDirective {
...
}
// app.module.ts
@NgModule({
declarations: [
AppComponent,
...
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
BrowserAnimationsModule,
...
],
providers: [...],
bootstrap: [AppComponent],
})
export class AppModule { }
// main.ts
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.error(err));
// main.ts
bootstrapApplication(
AppComponent, // main component entry point
{
providers: [
importProvidersFrom([
SomeModule,
SomeOtherModule.forRoot(...)
]),
provideRouter(APP_ROUTES),
provideHttpClient(withInterceptors([...])),
provideAnimations(),
]
}
)
import {
// All standalone
AsyncPipe,
JsonPipe,
NgForOf,
NgIf
} from "@angular/common";
[...]
@Component({
standalone: true,
imports: [
// CommonModule,
NgIf,
NgForOf,
AsyncPipe,
JsonPipe,
...
],
})
export class AppComponent {
[...]
}
RouterModule.forRoot([
{
path: '',
component: HomeComponent,
},
{
path: 'movies',
loadChildren: () =>
import('./movies/movies.module').then((m) => m.MoviesModule),
},
]),
bootstrapApplication(
AppComponent,
{
providers: [
provideRouter(APP_ROUTES),
]
}
)
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
export const APP_ROUTES: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'home'
},
{
path: 'home',
component: HomeComponent
},
// Option 1: Lazy Loading another Routing Config
{
path: 'movies',
loadChildren: () => import('./movies/movies.routes').then(m => m.MOVIES_ROUTES)
},
// Option 2: Directly Lazy Loading a Standalone Component
{
path: 'movies-list',
loadComponent: () => import('./movies/movies-list.component').then(m => m.MoviesListComponent)
},
[...]
];
export const MOVIE_ROUTES: Route[] = [
{
path: '',
loadComponent: () =>
import('./components/movie-list/movie-list.component').then(
(c) => c.MovieListComponent
),
},
{
path: 'new',
loadComponent: () =>
import('./components/movie-detail/movie-detail.component').then(
(c) => c.MovieDetailComponent
),
},
{
path: ':id',
loadComponent: () =>
import('./components/movie-detail/movie-detail.component').then(
(c) => c.MovieDetailComponent
),
},
];
export const MOVIE_ROUTES: Route[] = [
{
path: '',
loadComponent: () =>
import('./components/movie-list/movie-list.component').then(
(c) => c.MovieListComponent
),
},
{
path: 'new',
loadComponent: () =>
import('./components/movie-detail/movie-detail.component').then(
(c) => c.MovieDetailComponent
),
},
{
path: ':id',
loadComponent: () =>
import('./components/movie-detail/movie-detail.component').then(
(c) => c.MovieDetailComponent
),
},
];
import { provideHttpClient, withInterceptors } from "@angular/common/http";
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([authInterceptor]),
),
]
});
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authReq = req.clone({
headers: req.headers.set(
'Authorization',
`Bearer ${inject(AuthService).token()}`
),
});
return next(authReq);
};
ng generate @angular/core:standalone
? Choose the type of migration: (Use arrow keys)
❯ Convert all components, directives and pipes to standalone
Remove unnecessary NgModule classes
Bootstrap the application using standalone APIs
@if (ifCondition) {
<div>If template</div>
} @else if (elseIfCondition) {
<div>Else-if Template</div>
} @else {
<div>Else Template</div>
}
@if(movie$ | async; as movie) {
// movie available as block variable
}
<div *ngIf="ifCondition; else elseTemplate">
If template
</div>
<ng-template #elseTemplate>
<div>Else template</div>
</ng-template>
@for (movie of movies; track movie.id) {
} @empty {
<div> No movies in list </div>
}
// $count, $first, $last, $even, $odd.
@for (movie of movies; track movie.id) {
} @empty {
<div> No movies in list </div>
}
*ngFor="let movie of movies; trackBy: trackByFn"
<div [ngSwitch]="streamingService">
<div *ngSwitchCase="'AppleTV'">Ted Lasso</div>
<div *ngSwitchCase="'Disney+'">Mandalorian</div>
<div *ngSwitchDefault>Peaky Blinders</div>
</div>
@switch(streamingService) {
@case ('Disney+') {
<div>'Mandalorian'</div>
} @case ('AppleTV') {
<div>'Ted Lasso'</div>
} @default {
<div>'Peaky Blinders'</div>
}
}
ng generate @angular/core:control-flow
? Which path in your project should be migrated?
? Should the migration reformat your templates?
event
event
function addEventListener(eventName, callback) {
// call the real addEventListener
callRealAddEventListener(eventName, function() {
// first call the original callback
callback(...);
// and then run Angular-specific functionality
var changed = angular2.runChangeDetection();
if (changed) {
angular2.reRenderUIPart();
}
});
}
let x = 5;
let y = 3;
let z = x + y;
console.log(z); // 8
x = 10;
console.log(z); // 8
const x = signal(5);
const y = signal(3);
const z = computed(() => x() + y());
console.log(z()); // 8
x.set(10);
console.log(z()); // 13
quantity = signal(1);
qtyAvailable = signal([1, 2, 3, 4, 5, 6]);
selectedVehicle = signal<Vehicle>({
id: 1,
name: 'AT-AT',
price: 19416.13
});
vehicles = signal<Vehicle[]>([]);
quantity();
<div *ngFor="let q of qtyAvailable()">{{ q }}</div>
<div>Vehicle: {{ selectedVehicle().name }}</div>
<div>Price: {{ selectedVehicle().price }}</div>
this.quantity.set(qty);
// Update value based on current value
this.quantity.update(qty => qty * 2);
quantity.set(5);
quantity.set(42);
quantity.set(100); // final value
selectedVehicle = signal<Vehicle>({
id: 1,
name: 'AT-AT',
price: 19416.13
});
totalPrice = computed(() => this.selectedVehicle().price * this.quantity());
color = computed(() => this.totalPrice() > 50000 ? 'green' : 'blue');
Extended price: {{ totalPrice() }} // calculated
Total price: {{ totalPrice() }} // reused value
Amount due: {{ totalPrice() }} // reused value
constructor() {
effect(
() => console.log(this.selectedVehicle())
);
}
ngOnInit(): void {
// Effects are not allowed here.
effect(() => {
console.log('vehicle:', this.selectedVehicle());
});
}
inject
to get hold of the current DestroyRef
injector = inject(Injector);
ngOnInit(): void {
runInInjectionContext(this.injector, () => {
effect(() => {
console.log('route:', this.flightRoute());
});
});
}
effect(() => {
// Writing into signals is not allowed here:
this.to.set(this.from());
});
effect(() => {
this.to.set(this.from());
}, { allowSignalWrites: true })
effect(() => {
if(false) {
this.to.set(this.from()); // won't trigger effect when changed
}
});
object = signal(
{
id: 1,
title: "Angular For Beginners",
},
{
equal: (a, b) => {
return a.id === b.id && a.title == b.title;
},
}
);
title = computed(() => {
console.log(`Calling computed() function...`)
const course = this.object();
return course.title;
})
updateObject() {
this.object.set({
id: 1,
title: "Angular For Beginners"
});
}
counterObservable = interval(1000);
// Get a `Signal` representing the `counterObservable`'s value.
counter = toSignal(this.counterObservable, {initialValue: 0});
query: Signal<string> = inject(QueryService).query;
query$ = toObservable(this.query);
results$ = this.query$.pipe(
switchMap(query => this.http.get('/search?q=' + query ))
);
// optional
firstName = input<string>(); // InputSignal<string|undefined>
age = input(0); // InputSignal<number>
// required
lastName = input.required<string>(); // InputSignal<string>
changeMovie = output<string>()
changeMovie.emit(123)