Last updated: November 15, 2020 8:44 PM

Angular 8 Demo Project Log and Experience Part 2.

Lifecycle Hooks

The general Angular life cycle is below:



This life cycle has different hooks:

  • constructor(): This will always be called at first.

  • ngOnChanges(changes: SimpleChanges): void {}: Check changes of the component.

    • Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.
    • Called when input properties of component changes. changes is a dict type object (key: property name, value: SimpleChange).
    • Add implements OnChanges to the class.
  • ngOnInit(): Initialize the component. In this func we can safely use properties and methods of component.

  • ngAfterContentInit(): void {}: Initialize the content of component.

    • Called after ngOnInit when the component’s or directive’s content has been initialized.
    • Add implements AfterContentInit to the class.
    • By default, our component doesn’t support nesting. That is in the below example the Hello will not be displayed in the view. To display it, we add an <ng-content> tag in the ScrollableTabComponent to let the component have a content.
  • ngAfterContentChecked(): void {}: Check invalid data in the content of component. Called after every check of the component’s or directive’s content.

  • ngAfterViewInit(): Initialize the view of the component.

    • View includes the component and all its sub-components.
    • Called after ngAfterContentInit when the component’s view has been initialized. Applies to components only.
  • ngAfterViewChecked(): void {}: Check invalid data in the view. Called after every check of the component’s view. Applies to components only.

  • ngOnCheck(): void {}: Check invalid data of component.

    • Called every time that the input properties of a component or a directive are checked.
    • Use it to extend change detection by performing a custom check.
  • ngOnDestroy(): void {}: Called once, before the instance is destroyed. How to trigger this directive. We can use *ngIf, if it is falsy the component will be destroyed. For example, This is used when we want to do some clearance, e.g. when setting interval.

Code example and output

Add the code in ScrollableTabComponent

constructor() {
   console.log('constructor called');
 }
 ngOnChanges(changes: SimpleChanges): void {
   console.log('Component input changes, call ngOnChanges: ', changes);
 }
 ngOnInit() {
   console.log('ngOnInit called');
 }
 ngAfterContentInit(): void {
   console.log('ngAfterContentInit called');
 }
 ngAfterContentChecked(): void {
   console.log('ngAfterContentChecked called');
 }
 ngAfterViewInit(): void {
   console.log('ngAfterViewInit called');
 }
 ngAfterViewChecked(): void {
   console.log('ngAfterViewChecked called');
 }
 ngOnDestroy(): void {
   console.log('ngOnDestroy');
 }

The output is:

Template reference variables

Template reference variables I

We can use #refName to name the reference of template element. For example,

<div #helloDiv>Hello</div>

Since the scope of the refName is global, so it should be unique. Then we can use this refName as the reference of template element. In the following example, the @ViewChild is a selector, used to find the DOM element or component. ElementRef is a wrapper class of DOM element. Since DOM element is not an Angular class, so a wrapper class is needed to conveniently use and identify different types of DOM elements.

export class AppComponent {
  @ViewChild('helloDiv') helloDivRef: ElementRef;
}

Template reference variables II

we can also use type name. For example,

<app-image-slider [sliders]="imageSliders"></app-image-slider>
@ViewChild('ImageSliderComponent') imageSlider: ImageSliderComponent;

If we want to refer to multiple elements, we can use @ViewChildren(refName or typeName). Then declaration type should be QueryList<?>.For example,

<img
  #img
  *ngFor="let slider of sliders"
  [src]="slider.imgUrl"
  [alt]="slider.caption"
/>
@ViewChildren('img') imgs: QueryList<ElementRef>;

Then we can change style of DOM elements. Changing properties of DOM elements is always not recommended in Angular, since this may cause Injection. Instead we can use Renderer2 module. For example,

constructor (private rd2: Renderer2) {}

@ViewChildren('img') imgs: QueryList<ElementRef>;

ngAfterViewInit () {
  this.imgs.forEach(item => {
    this.rd2.setStyle(item.nativeElement, 'height', '100px')
  });
}

Summary

  • @ViewChild() can be used to ref the view elements. These elements can be Angular component or DOM elements.
  • In AfterViewInit, we can safely use elements referred by @ViewChild().
  • It’s better to use Renderer2 to operate DOM elements.

Use setInterval() method, letting each img scrollLeft a specific length every 2 seconds. We did this in ngAfterViewInit().

Use Renderer2 to safely change the property, DO NOT use DOM directly.

export interface ImageSlider {
  imgUrl: string;
  link: string;
  caption: string;
}

export class ImageSliderComponent implements OnInit, AfterViewInit, OnDestroy {
  @Input() sliders: ImageSlider[] = [];
  @Input() sliderHeight = '160xp';
  @Input() intervalBySeconds = 2;
  selectIndex = 0;
  intervalId;
  @ViewChild('imageSlider', { static: true }) imgSlider: ElementRef;

  constructor(private rd2: Renderer2) {}

  ngOnInit() {}

  ngAfterViewInit(): void {
    this.intervalId = setInterval(() => {
      this.rd2.setProperty(
        this.imgSlider.nativeElement,
        'scrollLeft',
        (this.getIndex(++this.selectIndex) *
          this.imgSlider.nativeElement.scrollWidth) /
          this.sliders.length
      );
    }, this.intervalBySeconds * 1000);
  }

  getIndex(idx: number): number {
    return idx >= 0
      ? idx % this.sliders.length
      : this.sliders.length - (Math.abs(idx) % this.sliders.length);
  }

  handleScroll(ev: any) {
    const ratio =
      (ev.target.scrollLeft * this.sliders.length) / ev.target.scrollWidth;
    this.selectIndex = Math.round(ratio);
  }

  ngOnDestroy(): void {
    clearInterval(this.intervalId);
  }
}
<div class="container" [ngStyle]="{ height: sliderHeight }">
  <div class="image-slider" (scroll)="handleScroll($event)" #imageSlider>
    <img
      #img
      *ngFor="let slider of sliders"
      [src]="slider.imgUrl"
      [alt]="slider.caption"
    />
  </div>
  <div class="nav-section">
    <span
      *ngFor="let _ of sliders; let idx = index"
      [ngClass]="{ 'slider-button-select': idx === selectIndex }"
      class="slider-button"
    >
    </span>
  </div>
</div>
.nav-section {
  position: absolute;
  bottom: 0;
  width: 100%;
  opacity: 0.5;
  color: #fff;
  background-color: #000000;
  display: flex;
  justify-content: flex-end;
  align-items: stretch;
}

.nav-section .slider-button {
  display: flex;
  width: 10px;
  height: 10px;
  background-color: #fff;
  text-decoration: none;
  border-radius: 50%;
  margin: 5px;
}

Call child component from parent component.

<app-image-slider [sliders]="imagesSliders" #imageSlider></app-image-slider>

The Reference name #imageSlider same as in the ts file @ViewChild.

We can safely use @ViewChild referenced element in ngAfterViewInit.

@ViewChild('imageSlider', { static: true }) imgSlider: ImageSliderComponent;
// @ViewChild(ImageSliderComponent) imgSlider: ImageSliderComponent;

imagesSliders: ImageSlider[] = [
    {
      imgUrl:
        'https://xxx',
      link: '',
      caption: ''
    },
]