Mastodon hachyterm.io

I’m learning Angular right now – as a React.js fangirl.

Pluralsight offered a free month of learning in April. I’ve taken advantage of it.

Here are some notes on the course Angular Component Communication by Deborah Kurata.

Angular Component Communication

Introduction

  • components need to communicate with each other, with its template, with the router, can use a service as an intermediary
  • check GitHub repository

Communication with a Template

Interpolation:

// src/app/app.component.ts
currentCustomer = 'Maria'
// src/app/app.component.html
<h3>Current customer: {{ currentCustomer }}</h3>

Property Binding:

// src/app/app.component.ts
imageWidth: number = 50
// src/app/app.component.html
<img [style.width.px]='imageWidth'>

Event Binding:

// src/app/app.component.ts
toggleImage(): void {
  this.showImage = !this.showImage;
}
// src/app/app.component.html
<button (click)='toggleImage()'>Show Image</button>
// src/app/app.component.ts
<p *ngIf="condition">Show this sentence unless the condition is true.</p>
  • “two-way binding” between template and component
// src/app/app.component.ts
listFilter: string = 'Cart';

onFilterChange(filter:string): void {
  this.listFilter = filter;
  this.performFilter(this.listFilter);
}
// src/app/app.component.html
<input type='text' [ngModel]='listFilter' (ngModelChange)='onFilterChange=($event)' />
  • getters and setters
// src/app/app.component.ts
private _listFilter: string;

get listFilter(): string {
  return this._listFilter;
}

set listFilter(value: string) {
  this._listFilter = value;
}
// src/app/app.component.html
<input type='text' [(ngModel)]='listFilter' />

ViewChild and ViewChildren

  • valueChanges observable
  • ViewChild
  • useful for template-driven forms
  • remember that a component is first constructed (constructor()) and initialized(ngOnInit()), after that the view initializes and renders
// src/app/app.component.ts
@ViewChild('filterElement') filterElementRef: ElementRef;

ngAfterViewInit(): void {
  this.filterElementRef.nativeElement.focus();
}
// src/app/app.component.html
<input type='text' #filterElement [(ngModel)]='listFilter' />
  • use NgAfterViewInit() to access the native HTML element (DOM)
  • subscribe to changes
// src/app/app.component.ts
listFilter: string;

@ViewChild(NgModel) filterInput: NgModel;

ngAfterViewInit(): void {
  this.filterInput.valueChanges.subscribe(() => this.performFilter(this.listFilter));
}
// src/app/app.component.html
<input type='text' #filterElement [(ngModel)]='listFilter' />
  • ViewChildren: returns a QueryList of element or directive references
  • method does not work well with *ngIf: in this case the @ViewChild might be undefined

Communicating with a Child Component

  • parent component’s template must contain the child component
  • components connected via routing don’t have a parent-child-relationship
// src/app/parent.component.html
<pm-criteria></pm-criteria>
// src/app/child.component.ts
@Component({
selector: 'pm-criteria',
templateUrl: './criteria.component.html',
styleUrls: ['./criteria.component.css']})

@Input example:

// src/app/parent.component.html
<pm-criteria [displayDetail]='includeDetail'></pm-criteria>
// src/app/parent.component.ts
includeDetail: boolean = true;

// src/app/child.component.ts
@Input() displayDetail: boolean;
  • Getter/Setter: favor if child only reacts to changes to specific properties
  • OnChanges: favor if child reacts to any input property changes or if you need to access current and prior values
  • Template Reference Variable: use from the parent’s template
  • @ViewChild use from the parent’s class

Communicating with a Parent Component

  • use cases: event notification (@Output), provide information to parent (Template Reference Variable, @Viewchild)
// src/app/parent.component.html
<pm-criteria
  [displayDetail]='includeDetail'
  (valueChange)='onValueChange($event)'>
</pm-criteria>
// src/app/parent.component.ts
includeDetail: boolean = true;

onValueChange(value: string): void {
  this.performFilter(value);
}

// src/app/child.component.ts
@Output() valueChange: EventEmitter<string> = new EventEmitter<string>();

private _listFilter: string;

get listFilter(): string {
  return this._listFilter;
}

set listFilter(value: string) {
  this._listFilter = value;
  this.valueChange.emit(value);
}
// src/app/app.component.html
<input type='text' [(ngModel)]='listFilter' />

Communication Through a Service

  • state: view state, user information, entity data, user selection and input, etc.
  • managing state: property bag, basic state management (via service), state management with notifications (services with BehaviorSubject), ngrx/Redux

Example Property Bag:

// src/app/param.service.ts
@Injectable()
export class ParamService {
  showImage: boolean
  filterBy: string
}

// src/app/product.list.component.ts
export class ProductListComponent {
  get ShowImage(): boolean {
    return this.paramService.showImage
  }
  get listFilter(): string {
    return this.paramService.filterBy
  }
  set listFilter(value: string) {
    this.paramservice.filterBy = value
  }
}
  • property bags are great for retaining view state & user selections
  • property bags can also communicate with other components
  • great for sharing data or other state & communicating state changes
  • caveat: any component can read and change values
  • components are only notified of state changes if they use template binding
  • consider service scope and lifetime

Communicating Through a State Management Service

  • good for retrieving entity state and share with others
  • purpose: provide state values, maintain and update state, observe state changes
  • add a property to retain the list - on get: return the list - on get by id: return an item from the list - on create: add the item the list - on delete: remove the item from the list
// src/app/product.service.ts
private products: IProduct[];

getProducts(): Observable<IProduct[]> {
  if (this.products) {
    return of(this.products);
  }
  return this.http.get<IProduct[]>(this.productsUrl)
    .pipe(
      tap(data => console.log(JSON.stringify(data))),
      tap(data => this.products = data),
      catchError(this.handleError)
      );
}
  • benefits: encapsulates retrieve and store operations, retains and shares state values, caches values, provides change notification for bound values using a getter
  • cons: stale data, no explicit change notification, state is not immutable

Communicating Through Service Notification

  • you can use Subject or BehaviorSubject (multi-cast) to broadcast changes
// src/app/product-list.component.html
<button type='button'
  *ngFor='let product of products'
  (click)='onSelected(product)'>
// src/app/product-list.component.ts
onSelected(product: IProduct) {
  this.productService.changeSelectedProduct(product);
}

// src/app/product.service.ts
private selectedProductSource = new Subject<IProduct>();
selectedProductChanges$ = this.selectedProductSource.asObservable();

changeSelectedProduct(selectedProduct: IProduct) {
  // broadcast the notification
  this.selectedProductSource.next(selectedProduct);
}

// Component or Service
onSelected(product: IProduct) {
  // listen for and respond to the notification
  this.productService.changeSelectedProduct(product);
}

Communicating Using the Router

  • route parameters: required, optional, query parameters
  • benefits: simple, resulting URLs are bookmarkable & sharable
  • cons: parameters appear in the URL, not good for large amounts of data

Thoughts

Deborah Kurata is an excellent teacher who can break concepts down to a digestible level.
The course slides and examples helped me immensely.

All in all, the class proved to be fantastic for people new to Angular.

I am sure that I will reference the course’s GitHub repository in the future.

Resources