How to use ViewChild in Angular 9

How to use @ViewChild() in Angular 9

Get right to the live example

What is @ViewChild() and when should we use it?

ViewChild is a decorator and it’s mostly used within angular when you need to select a particular element from the view.

Table of contents:

1. Select a particular directive

2. Select a particular HTML element

3. Difference between ViewChild { static: false } and { static: true }

4. Using read to inject a different configuration object

The following examples will use Angular 9, I say this because starting from version 9 you don’t need to specify the second argument which defaults to false ({ static: false }), anything else remains the same.

1. Select a particular directive

@Directive({selector: 'child-directive'})
class ChildDirective {
  @Input() id! :number;
  ngAfterViewInit() {
    console.log("ngAfterViewInit child")
  }
}

@Component({
  selector: 'select-directive',
  template: `
    <child-directive id="1"></child-directive>
  `
})
class SelectDirectiveComponent implements OnInit, AfterViewInit {
  @ViewChild(ChildDirective) child!: ChildDirective;

  ngAfterViewInit() {
    console.log("ngAfterViewInit parent with child = ", this.child);
  }
}

As you might seen above it’s pretty simple to select an html element from the view.

By using @ViewChild(ChildDirective) child: ChildDirective; we tell angular that we need to inject into our reference variable child the element from our view template with type ChildDirective.

By default angular will search for our ChildDirective after all the elements from our view have been created and the bindings have been initiated. Will see later, how to get the reference to our view before all the binding has been performed.

2. Select a particular element from view

@Component({
  selector: 'select-element',
  template: `
    <div #myDiv>
    </div>
  `
})
class SelectElementComponent implements AfterViewInit {
  @ViewChild('myDiv') child: HTMLElement;

  ngAfterViewInit() {
    console.log("ngAfterViewInit", this.child);
  }
}

Selecting an html element from view, it’s pretty similar with selecting a directive, the main changes are with the aguments that we pass to our @ViewChild decorator.

Instead of using a type, we are going to specify the element that needs to be selected by using a string.

In our example above we are using #myDiv to identify our element in the view and @ViewChild('myDiv') to tell angular that we need to select that spcific element from our view.

3. Difference between ViewChild static false and static true properties

If you had a chance to look over ViewChild decorator you might have seen that @ViewChild decorator accepts 2 arguments.

Now we are going to explore what ca we do if we are to use the second argument.

The second argument that ViewChild receives is an object which can have two properties static and read.

Let’s talk first about the static option. Before Angular 9, this static option had a value of true by default.

What { static: true } does?

Having static set to true will result in telling angular that we need to get the reference to that target element as soon as the component is created, however this means that we are going to get the reference before our element had a chance to bind the inputs and init it’s view.

Example

@Directive({selector: 'child-directive'})
class ChildDirective {
  @Input() id! :number;
  ngAfterViewInit() {
    console.log("child");
  }
}

@Component({
  selector: 'select-directive',
  template: `
    <child-directive id="1"></child-directive>
  `
})
class SelectDirectiveComponent implements AfterViewInit {
  @ViewChild(ChildDirective, { static: true}) child: ChildDirective;

  ngOnInit() {
    console.log("ngOnInit", this.child);
  }

  ngAfterViewInit() {
    console.log("ngAfterViewInit", this.child);
  }
}

Will print:

ngOnInit ChildDirective {}

ngAfterViewInit child

ngAfterViewInit ChildDirective {id: "1"}

As you have seen, only after the child has it’s view created we will see the data for our ChildDirective available in ngAfterViewInit from parent element.

When should you use { static: true } ?

One usecase could be, when you need to access a child element’s instance fields that don’t rely for their values on inputs from other elements.

Example:

@Directive({selector: 'child-directive'})
class ChildDirective {
  @Input() id! :number;
  public childName = 'childName';
  ngAfterViewInit() {
    console.log("child ngAfterViewInit");
  }
}

@Component({
  selector: 'select-directive',
  template: `
    <child-directive id="1"></child-directive>
  `
})
class SelectDirectiveComponent implements AfterViewInit {
  @ViewChild(ChildDirective, { static: true}) child: ChildDirective;

  ngOnInit() {
    console.log("ngOnInit", this.child);
  }

  ngAfterViewInit() {
    console.log("ngAfterViewInit", this.child);
  }
}

So, { static: true } only makes sense when you have a child that’s using an instance field with a predefined value.

What { static: false } does?

It means that angular is going to look for our target element only after the view has been created and in most of the cases this is the way you’ll use it.

Example:

@Directive({selector: 'child-directive'})
class ChildDirective {
  @Input() id! :number;
  public childName = 'childName';
  ngAfterViewInit() {
    console.log("ngAfterViewInit child");
  }
}

@Component({
  selector: 'select-directive',
  template: `
    <child-directive id="1"></child-directive>
  `
})
class SelectDirectiveComponent implements AfterViewInit {
  @ViewChild(ChildDirective, { static: false}) child: ChildDirective;

  ngOnInit() {
    console.log("ngOnInit", this.child);
  }
  ngAfterViewInit() {
    console.log("ngAfterViewInit", this.child);
  }
}

Will output something like this:

ngOnInit undefined

ngAfterViewInit child

ngAfterViewInit ChildDirective {childName: "childName", id: "1"}

So, this is the most used approach and it will make angular look for our target element only after the view has been created.

4. Using read to inject a different configuration object

export const TestInjectable = new InjectionToken<Inject>('someToken');

interface Inject {
  val: string;
}
@Directive({selector: 'child-directive',
providers: [{ 
    provide: TestInjectable, useValue: {val: 'someValue'}
  }]
})
class ChildDirective {
  @Input() id! :number;
  public childName = 'childName';
  ngAfterViewInit() {
    console.log("ngAfterViewInit child");
  }
}

@Component({
  selector: 'select-directive-read',
  template: `
    <child-directive id="1"></child-directive>
  `
})
class SelectDirectiveReadComponent implements AfterViewInit {
  @ViewChild(ChildDirective, { read: TestInjectable}) child: Inject;
  @ViewChild(ChildDirective, { read: ElementRef}) childTypeElementRef: ElementRef;
  @ViewChild(ChildDirective, { read: ChildDirective}) childTypeChildrenDirective: ChildDirective;

  ngAfterViewInit() {
    console.log("ngAfterViewInit", this.child);
    console.log("ngAfterViewInit", this.childTypeElementRef);
    console.log("ngAfterViewInit", this.childTypeChildrenDirective);
  }
}

When you run this code, you'll see that:

  1. this.childTypeElementRef will have an instance of ElementRef
  2. this.child will have an instance of TestInjectable
  3. this.childTypeChildrenDirective will have an instance of ChildDirective

Why does all the the above code works?

Thanks to the read configuration parameter, we can specify for our ViewChild decorator that we need to inject a specific instance from the element that angular has queried for.

Using InjectionToken we provided a value of type TestInjectable for ChildDirective and this means we can use @ViewChild(ChildDirective, { read: TestInjectable}) child: Inject; to get that specific instance from our ChildDirective.

For others instances like ElementRef and ChildDirective angular will perform the same checks.

When should we use the read option?:

When we need to get an injectable instnace from a child element it’s useful to use a ViewChild.

Live example also available here


Stay tuned to get more tutorials on Web development

Follow me on twitter

Get The Best Of All Posts Delivered To Your Inbox

Subscribe to our newsletter and stay updated.