The following are course notes when watching this course (recorded with Angular v5) but coding along with Angular v14
-
As of Angular 12, strict typing is on by default. This requires that every variable have an initial value (unless the variable is optional) and that types are specified if they can't be inferred. (Some code on the slides shows variables without an initial default value.)
-
TypeScript will infer the type when a specific default value is provided. No need to provide both the type and default. (Some code on the slides show both the type and a default value. The inferred types were removed from the provided code.)
-
New syntax is required to access the form errors collection:
*ngIf="productCodeVar.errors?.['required']"
-
The folder structure of the starter files have changed slightly due to changes in the files generated by the Angular CLI. For example, there is no longer an e2e folder and no tslint.json.
See the
Angular: Getting Started
course for details on the files and folders generated by the Angular CLI. -
Updated from Bootstrap 3 to Bootstrap 5, which changed:
- The the layout from
panel
tocard
. - Many of the
nav
style classes. - Many of the form style classes.
- Many of the validation style classes.
- Bootstrap no longer provides icons, use font-awesome instead for fonts.
- The the layout from
-
Updated from RxJS v5 to v7 which changed:
- How RxJS creation functions and operators are imported. Everything is now imported from
rxjs
. - Syntax of the
subscribe
method. It now only takes one parameter: either the next function or an object withnext
andcomplete
properties.
See the
Angular: Getting Started
or theRxJS in Angular: Reactive Development
course on Pluralsight for more information about RxJS. - How RxJS creation functions and operators are imported. Everything is now imported from
- The blog is no longer maintained. Please use the GitHub repository for the most up-to-date information about this course.
- If you are new to Angular, consider watching the
Angular: Getting Started
course first. Note that theAngular First Look
course has not been updated for recent versions of Angular. - Consider using the
APM-Start v14
files to code along with this course. The originalAPM-Start
files are for Angular v5 and don't install easily with current versions of node. Use these course notes while coding along for any changes to the entered code, primarily for strict typing.
See 'All Modules' above.
See 'All Modules' above.
When using @ViewChild
and @ViewChildren
, define the property as optional using a ?
and specify the appropriate type. The type is then the type OR undefined and the default value is undefined. The property must then be checked for null or undefined before it is used.
Changes when coding along:
@ViewChild('filterElement') filterElementRef?: ElementRef;
- CHANGE: Added
?
to mark the property as optional. - REASON: As of Angular v12, Angular is strongly typed by default. Strong typing requires all variables to have a valid type (unless the type can be inferred from the initial value) and be initialized or marked as optional.
ngAfterViewInit(): void {
if (this.filterElementRef?.nativeElement) {
this.filterElementRef.nativeElement.focus();
}
}
- CHANGE: Added check for null or undefined using the safe navigation operator (
?
). - REASON: Now that the
filterElementRef
can be undefined (because it was defined as optional), the object must be checked before accessing its properties or methods.
@ViewChildren('filterElement, nameElement') inputElementRefs?: QueryList<ElementRef>;
- CHANGE: Added
?
to mark the property as optional. - REASON: As of Angular v12, Angular is strongly typed by default.
@ViewChild(NgModel) filterInput?: NgModel;
- CHANGE: Added
?
to mark the property as optional. - REASON: As of Angular v12, Angular is strongly typed by default.
ngAfterViewInit(): void {
this.filterInput?.valueChanges?.subscribe(
() => this.performFilter(this.listFilter)
}
}
- CHANGE: Added check for null or undefined using the safe navigation operator (
?
). - REASON: Now that the
filterInput
can be undefined (because it was defined as optional), the object must be checked before accessing its properties or methods.
This is code is part of the starter files and doesn't need to be entered. It is provided here for reference.
saveProduct(): void {
if (this.editForm?.valid && this.product) {
this.productService.saveProduct(this.product)
.subscribe({
next: () => {
if (this.product && this.originalProduct) {
// Assign the changes from the copy
for (let key in this.product)
(this.originalProduct as any)[key as keyof IProduct] = this.product[key as keyof IProduct];
this.onSaveComplete();
}
},
error: err => this.errorMessage = err
});
} else {
this.errorMessage = 'Please correct the validation errors.';
}
}
- CHANGES:
- Added the safe navigation operator (
?
) as needed for properties that could be null or undefined. - Added additional checking for null or undefined in the
if
statements. - Changed to new
subscribe
syntax. - Replaced
Object.keys
with afor...in
loop to copy each property from the updated product to the original product.
- Added the safe navigation operator (
- REASONS:
- As of Angular v12, Angular is strongly typed by default.
- The RxJS
subscribe
syntax has changed. - When caching the products in the Product Service, the code assumes that edits are done on the object in the array (not on a copy). So each property of the edited copy must be assigned back to the original object.
NOTE: It may be better to make the products in the array immutable, work with a copy using the spread operator and instead replace the element in the array with the changed item.
See 'All Modules' above.
Changes when coding along:
listFilter = '';
@Input() displayDetail = false;
@Input() hitCount = 0;
hitMessage = '';
@ViewChild('filterElement') filterElementRef?: ElementRef;
- CHANGE: Properties require a default value or must be declared as optional. Also, properties with a default value don't need a type if that type can be inferred.
- REASON: As of Angular v12, Angular is strongly typed by default.
ngAfterViewInit(): void {
if (this.filterElementRef?.nativeElement) {
this.filterElementRef.nativeElement.focus();
}
}
- CHANGE: Added check for null or undefined using the safe navigation operator (
?
). - REASON: Now that the
filterElementRef
can be undefined (because it was defined as optional), the object must be checked before accessing its properties or methods.
@ViewChild(CriteriaComponent) filterComponent?: CriteriaComponent;
parentListFilter = '';
- CHANGE: Mark the
@ViewChild
property as optional so no default value is needed. - REASON: As of Angular v12, Angular is strongly typed by default.
ngAfterViewInit(): void {
if (this.filterComponent) {
this.parentListFilter = this.filterComponent.listFilter;
}
}
- CHANGE: Added check of the
filterComponent
property before using. - REASON: The
filterComponent
property has a default value of undefined, so that property must be checked for null or undefined before accessing its properties or methods.
See 'All Modules' above.
Changes when coding along:
private _listFilter = '';
- CHANGE: Properties require a default value or must be declared as optional. Also, properties with a default value don't need a type if that type can be inferred.
- REASON: As of Angular v12, Angular is strongly typed by default.
See 'All Modules' above.
As of Angular v6, service registration is now defined in the @Injectable
decorator of the service itself instead of in a module.
See Angular: Getting Started
for more information on registering services using the @Injectable
decorator.
For more information on NgRx, check out Angular NgRx: Getting Started
Changes when coding along:
ng g s products/product-parameter
- CHANGE: No need for the -m property.
- REASON: The service does not require registration in the module. The CLI instead generates the registration as part of the
@Injectable
decorator.
NOTE: The product.module.ts file no longer has nor needs a
providers
array.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ProductParameterService {
showImage = false;
filterBy = '';
constructor() { }
}
- CHANGES:
- Register the service using the
@Injectable
decorator. - Properties require a default value or must be declared as optional. Also, properties with a default value don't need a type if that type can be inferred
- Register the service using the
- REASON
- As of Angular v6, using the
@Injectable
decorator provides better "tree shaking" and is now best practice for registering services. - As of Angular v12, Angular is strongly typed by default.
- As of Angular v6, using the
@ViewChild(CriteriaComponent) filterComponent?: CriteriaComponent;
- CHANGE: Mark the
@ViewChild
property as optional so no default value is needed. - REASON: As of Angular v12, Angular is strongly typed by default.
ngOnInit(): void {
this.productService.getProducts().subscribe({
next: products => {
this.products = products;
setTimeout(() => {
if (this.filterComponent) {
this.filterComponent.listFilter =
this.productParameterService.filterBy;
}
})
},
error: err => this.errorMessage = err
});
}
- CHANGES:
- The code to set the listFilter in the child component must be within a
setTimeout()
function to defer execution until the next evaluation cycle. - Changed to new
subscribe
syntax.
- The code to set the listFilter in the child component must be within a
- REASON:
- To allow referencing the
ViewChild
property within the ngOnInit (instead of AfterViewInit) and to prevent anExpression has changed after it was checked
error.REFERENCE: Angular Debugging "Expression has changed after is was checked": Simple Explanation (and Fix)
- The RxJS
subscribe
syntax has changed.
- To allow referencing the
See 'All Modules' above.
As of Angular v6, service registration is now defined in the @Injectable
decorator of the service itself instead of in a module.
See Angular: Getting Started
for more information on registering services using the @Injectable
decorator.
Changes when coding along:
This code is part of the starter files and doesn't need to be entered. It is provided here for reference.
import { catchError, Observable, of, tap, throwError } from 'rxjs';
- CHANGE: Import location is changed.
- REASON: For RxJS 7, all Observable creation functions, operators, and features are imported from
rxjs
.
@Injectable({
providedIn: 'root'
})
export class ProductService {
-
CHANGE: Register the service using the
@Injectable
decorator. -
REASON: As of Angular v6, using the
@Injectable
decorator provides better "tree shaking" and is now best practice for registering services.
This code is part of the coding along.
private products?: IProduct[];
- CHANGE: Mark the
products
property as optional so it doesn't require a default value. - REASON: As of Angular v12, Angular is strongly typed by default.
private createProduct(product: IProduct, headers: HttpHeaders): Observable<IProduct> {
product.id = null;
return this.http.post<IProduct>(this.productsUrl, product, { headers })
.pipe(
tap(createdProduct => console.log('createProduct:', JSON.stringify(createdProduct))),
tap(createdProduct => {
if (this.products) {
this.products.push(createdProduct);
}
}),
catchError(this.handleError)
);
}
- CHANGES:
- Check if the products array exists before pushing to it.
- Change the
data
variable used for the emitted item in the RxJS pipelines to a more descriptive name.
- REASON:
- If the user selected to add before viewing the list of products, the products won't yet be retrieved and cached so no need to push the new value to the array.
- Easier to understand what the emitted item is if the variable name is more specific.
deleteProduct(id: number): Observable<IProduct> {
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
const url = `${this.productsUrl}/${id}`;
return this.http.delete<IProduct>(url, { headers })
.pipe(
tap(() => console.log('deleteProduct:', id)),
tap(() => {
if (this.products) {
const foundIndex = this.products.findIndex(item => item.id === id);
if (foundIndex > -1) {
this.products.splice(foundIndex, 1);
}
}
}),
catchError(this.handleError)
);
}
- CHANGES:
- Check if the products array exists before locating the product in the array.
- Change the
data
variable used for the emitted item in the RxJS pipelines to()
when the emitted item is not used.
- REASON:
- The products array could be null or undefined, so check before using its properties and methods.
- No variable needed if the emitted item isn't used.
currentProduct: IProduct | null = null;
- CHANGE: Assign a default value.
- REASON: As of Angular v12, Angular is strongly typed by default.
import { timer } from 'rxjs';
ngOnInit() {
timer(0, 1000).subscribe(t => console.log(this.prod));
}
- CHANGE: Import location.
- REASON: For RxJS 7, all Observable creation functions, operators, and features are imported from
rxjs
.
See 'All Modules' above.
Reference to the current RxJS documentation.
See the
RxJS in Angular: Reactive Development
course on Pluralsight for more information about RxJS.
import { Subject, catchError, Observable, of, tap, throwError } from 'rxjs';
- CHANGE: Import location.
- REASON: For RxJS 7, all Observable creation functions, operators, and features are imported from
rxjs
.
// In createProduct
this.changeSelectedProduct(createdProduct);
- CHANGE: Changed variable name from
data
tocreateProduct
. - REASON: Easier to understand what the emitted item is if the variable name is more specific.
import { BehaviorSubject, catchError, Observable, of, tap, throwError } from 'rxjs';
- CHANGE: Import location.
- REASON: For RxJS 7, all Observable creation functions, operators, and features are imported from
rxjs
.
product: IProduct | null = null;
- CHANGE: Assign a default value.
- REASON: As of Angular v12, Angular is strongly typed by default.
sub?: Subscription;
ngOnDestroy(): void {
if (this.sub) {
this.sub.unsubscribe();
}
}
- CHANGES:
- Make the
sub
property optional. - Since the property is optional, check it for null or undefined before unsubscribing
- Make the
- REASON: As of Angular v12, Angular is strongly typed by default.
selectedProduct: IProduct | null = null;
- CHANGE: Assign a default value.
- REASON: As of Angular v12, Angular is strongly typed by default.
See 'All Modules' above.
See 'All Modules' above.
Learning More
- See the
Angular: Getting Started
course on Pluralsight for more information on many of the newer features of Angular, including strong typing. - See the
RxJS in Angular: Reactive Development
course on Pluralsight for more information about RxJS. - See the
Angular Routing
course on Pluralsight for more information on routing. - See the
Angular Reactive Forms
course on Pluralsight for more information on create, update, and delete operations. - See the
Angular NgRx: Getting Started
course on Pluralsight for more information on NgRx.