Skip to content

Latest commit

 

History

History
416 lines (345 loc) · 17.7 KB

COURSENOTES-v14.md

File metadata and controls

416 lines (345 loc) · 17.7 KB

Angular Component Communication Course Notes

The following are course notes when watching this course (recorded with Angular v5) but coding along with Angular v14

All Modules

  • 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 to card.
    • 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.
  • 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 with next and complete properties.

    See the Angular: Getting Started or the RxJS in Angular: Reactive Development course on Pluralsight for more information about RxJS.

Module 2

  • 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 the Angular 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 original APM-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.

Module 3

See 'All Modules' above.

Module 4

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:

product-list.component.ts

@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.

product-edit.component.ts

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 a for...in loop to copy each property from the updated product to the original product.
  • 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.

Module 5

See 'All Modules' above.

Changes when coding along:

criteria.component.ts

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.

product-list.component.ts

@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.

Module 6

See 'All Modules' above.

Changes when coding along:

criteria.component.ts

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.

Module 7

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:

Angular CLI command

  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.

product-parameter.service.ts

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
  • 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.

product-list.component.ts

@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.
  • REASON:

Module 8

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:

product.service.ts

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.

Module 9

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.

product.service.ts

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 to createProduct.
  • 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-shell-detail.component.ts

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
  • REASON: As of Angular v12, Angular is strongly typed by default.

product-shell-list.component.ts

  selectedProduct: IProduct | null = null;
  • CHANGE: Assign a default value.
  • REASON: As of Angular v12, Angular is strongly typed by default.

Module 10

See 'All Modules' above.

Module 11

See 'All Modules' above.

Learning More