Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - 'Listen' on @ViewChild changes

Tags:

angular

How can be a function like onElChanged be implemented, so that this functions gets executed each time properties of <div #plot id="plot-container"></div> changed?

component.html

<div #plot id="plot-container"></div>

component.ts

@ViewChild('plot') el: ElementRef;

onElChanged(){
  console.log(this.el.nativeElement.clientWidth);
}
like image 417
d4rty Avatar asked May 16 '18 15:05

d4rty


People also ask

Is it good to use ViewChild in Angular?

The Angular @ViewChild decorator is one of the first decorators that you will run into while learning Angular, as it's also one of the most commonly used decorators. This decorator has a lot of features: some of them might not be very well known but they are extremely useful.

What is the difference between ViewChild () and ContentChild ()?

ViewChild is used to select an element from component's template while ContentChild is used to select projected content.

What is difference between ViewChild and ViewChildren?

The ViewChild or ViewChildren decorators are used to Query and get the reference of the DOM element in the Component. ViewChild returns the first matching element and ViewChildren returns all the matching elements as a QueryList of items. We can use these references to manipulate element properties in the component.

What type does ViewChild return?

The ViewChild decorator returns the first element that matches a given directive, component, or template reference selector.


Video Answer


2 Answers

Though not a solution that uses @ViewChild decorator, you can use the very similar @ViewChildren decorator to subscribe to changes despite your particular observed subject being a non-list-like singular element.

You can use the first (or last) QueryList member to obtain the ElementRef value - ignoring most of the list-specific parts of the QueryList interface - which is the quick and dirty approach I use below. However, it is probably recommended to handle a singular element iteratively as a list of length 1 to stick with the typical @ViewChildren paradigm and create more generically useful methods and services.

@ViewChildren('plot', {read: ElementRef}) el: QueryList<ElementRef>;

/**
 * Listen within this component
 */
private listenForPlotChanges() {
    this.el.changes.subscribe(
        (next: QueryList<ElementRef>) => {
            console.log('listenForPlotChanges next:', next);

            const plot = next.first.nativeElement;
            console.log('listentForPlotChanges plot:', plot);
         }
    );
}

Or an example from a listener that exists outside a component (with special consideration for illustrating an update to the element via Renderer2)...

import { ElementRef, Injectable, QueryList, Renderer2, RendererFactory2 } from '@angular/core';
import { ISubscription } from 'rxjs/Subscription';

/**
 * Listen and update with renderer as a service outside a component
 *
 * Usage:
 *  @ViewChildren('yourElement', {read: ElementRef}) el: QueryList<ElementRef>;
 *
 *  constructor(listener: ListenerService) {}
 *
 *  ngAfterViewInit() {
 *    this.listener.listenForPlotChanges(this.el);
 *  }
 */
@Injectable()
export class ListenerService {

private renderer: Renderer2;

constructor(rendererFactory: RendererFactory2) {
    this.renderer = rendererFactory.createRenderer(null, null);
}

listenForPlotChanges(elementQueryList: QueryList<ElementRef>): ISubscription {

    const resolveNext = (next) => {
            console.log('listenForPlotChanges next:', next);

            const plot = next.first.nativeElement;
            console.log('listentForPlotChanges plot:', plot);

            this.renderer.addClass(plot, 'twist');
    }

    return elementQueryList.changes.subscribe(
        (next: QueryList<ElementRef>) => {
            resolveNext(next);
        }
    }
}

I've used my suggested approach in Angular version ^5.2.1 and have not verified for other versions of Angular including the latest.

like image 138
jmmygoggle Avatar answered Sep 18 '22 18:09

jmmygoggle


You could create a directive that uses MutationObserver to listen to attribute changes in the DOM for your specific element.

dom-change.directive.ts

import { Directive, ElementRef, EventEmitter, OnDestroy, Output } from '@angular/core';

@Directive({
  selector: '[domChange]'
})
export class DomChangeDirective implements OnDestroy {
  private changes: MutationObserver;

  @Output()
  public domChange = new EventEmitter();

  constructor(private elementRef: ElementRef) {
    const element = this.elementRef.nativeElement;

    this.changes = new MutationObserver((mutations: MutationRecord[]) => {
        mutations.forEach((mutation: MutationRecord) => this.domChange.emit(mutation));
      }
    );

    this.changes.observe(element, {
      attributes: true
    });
  }

  ngOnDestroy(): void {
    this.changes.disconnect();
  }
}

component.html

<div id="plot-container" (domChange)="onDomChange($event)"></div>

component.ts

onDomChange($event: Event): void {
    console.log($event);
  }
like image 30
Narm Avatar answered Sep 18 '22 18:09

Narm